mirror of
https://github.com/bryanthaboi/gen1recomp.git
synced 2026-08-18 11:44:42 +02:00
big ui moment
This commit is contained in:
@@ -0,0 +1,576 @@
|
||||
-- modules/behaviors/TextEditable.lua
|
||||
--
|
||||
-- Concrete behavior: TextEditor subsystem ownership — text editing, cursor
|
||||
-- management, text selection, text-related input handling, and text-editor
|
||||
-- state save/restore.
|
||||
--
|
||||
-- This behavior consolidates the legacy `if self._textEditor` nil-guard
|
||||
-- patterns that previously lived inside Element.lua:
|
||||
--
|
||||
-- * TextEditor creation + immediate-mode state restore (formerly
|
||||
-- Element:_initSubSystems lines ~813-830 — the `if self.editable then
|
||||
-- self._textEditor = Element._TextEditor.new {...}` block).
|
||||
-- * Cursor-blink update (formerly Element:update line ~2810 —
|
||||
-- `if self._textEditor then self._textEditor:update(self, dt) end`).
|
||||
-- * The 27 text-editor delegate methods (formerly Element:setText /
|
||||
-- getText / setCursorPosition / setSelection / focus / textinput /
|
||||
-- keypressed / _handleTextClick / _handleTextDrag / ...). Each was a 3-line
|
||||
-- nil-guard stub (check `_textEditor`, forward call, end). They are now
|
||||
-- module-level functions on this behavior; Element retains only 1-line
|
||||
-- forwarders that route through `Element._TextEditable.<fn>(self, ...)`.
|
||||
-- * Text-editor state save/restore (formerly the textEditor branch of
|
||||
-- Element:saveState / Element:restoreState), including the cursor/selection
|
||||
-- field sync and the text-selection drag-tracking fields
|
||||
-- (`_mouseDownPosition` / `_textDragOccurred`).
|
||||
--
|
||||
-- Element retains the `self._textEditor` field for backward-compat field
|
||||
-- access (Renderer:drawText reads it directly for cursor/selection rendering);
|
||||
-- runtime state lives ON THE ELEMENT. The behavior itself is stateless +
|
||||
-- immutable + shared across elements.
|
||||
--
|
||||
-- State ownership (per the locked Behavior contract):
|
||||
-- * Per-element runtime state lives ON THE ELEMENT (`self._textEditor`,
|
||||
-- `self._mouseDownPosition`, `self._textDragOccurred`). The behavior
|
||||
-- instance is stateless + immutable and shared across all editable
|
||||
-- elements.
|
||||
-- * Element-class-level dependencies are resolved via `getmetatable(element)`
|
||||
-- (which IS the Element class set by Element._construct), so the hook
|
||||
-- signature stays exactly `(element, ...)` with no DI parameters.
|
||||
--
|
||||
-- onDraw is a no-op: text/cursor/selection rendering stays in the Renderer's
|
||||
-- command buffer (Layer 4 "text"), driven by the Thamed behavior's single
|
||||
-- `Renderer:draw` call. The Renderer's `drawText` already reads
|
||||
-- `element._textEditor` for cursor/selection, so TextEditable OWNS the
|
||||
-- subsystem that drawText consumes, but the draw dispatch stays in the
|
||||
-- renderer to preserve the unified transform/scissor command-buffer ordering
|
||||
-- (mirrors Selectable.onDraw's no-op precedent, where rendering is owned by a
|
||||
-- different layer). Hoisting drawText into this behavior's onDraw would
|
||||
-- double-render text, since the Renderer command buffer already emits a "text"
|
||||
-- layer for every element.
|
||||
|
||||
local _pkg = (...):match("^(.-)behaviors%.") or "modules."
|
||||
local Behavior = require(_pkg .. "Behavior")
|
||||
|
||||
-- Resolve the Element class from an element instance (mirrors Clickable /
|
||||
-- Selectable). `setmetatable({}, Element)` in `_construct` makes the instance
|
||||
-- metatable BE the Element class, so this yields Element._TextEditor,
|
||||
-- Element._textEditorDeps, Element._Context, Element._StateManager, etc.
|
||||
-- without threading deps through the hook signature.
|
||||
local function ElementClass(element)
|
||||
return getmetatable(element)
|
||||
end
|
||||
|
||||
-- ============================================================================
|
||||
-- shouldAttach (class-level predicate, no element required)
|
||||
-- ============================================================================
|
||||
|
||||
-- Mirrors the spec predicate: attach when the element is text-editable OR
|
||||
-- carries text content. onAttach only ALLOCATES a TextEditor when
|
||||
-- `element.editable` is true (preserving the pre-refactor creation invariant
|
||||
-- "TextEditor created iff editable"), so non-editable text labels attach the
|
||||
-- behavior but allocate no TextEditor — their onUpdate/onDraw/saveState are
|
||||
-- nil-guarded no-ops, and the Element forwarders route them through the
|
||||
-- non-editable branch of each delegate function (reads/writes `element.text`
|
||||
-- directly). This keeps shouldAttach faithful to the spec while preserving
|
||||
-- exact pre-refactor allocation behavior.
|
||||
local function shouldAttach(props)
|
||||
props = props or {}
|
||||
return props.editable == true or props.text ~= nil
|
||||
end
|
||||
|
||||
-- ============================================================================
|
||||
-- onAttach — create the TextEditor (formerly Element:_initSubSystems lines
|
||||
-- ~813-830) and restore immediate-mode TextEditor state.
|
||||
-- ============================================================================
|
||||
|
||||
local function onAttach(element)
|
||||
local Element = ElementClass(element)
|
||||
|
||||
-- Only editable elements own a TextEditor. Preserves the exact pre-refactor
|
||||
-- creation guard (`if self.editable then ... end`) — non-editable text
|
||||
-- elements attach the behavior (so their forwarders route through a single
|
||||
-- code path) but allocate no TextEditor.
|
||||
if not element.editable then
|
||||
return
|
||||
end
|
||||
|
||||
-- Config is sourced from element fields (bound by _applyProps / _initVisualState
|
||||
-- before _attachBehaviors runs at the tail of Element.new) — NOT from raw
|
||||
-- props. The callbacks (onFocus/onBlur/onTextInput/onTextChange/onEnter) are
|
||||
-- schema-bound element fields by this point, and `element.text` is set by
|
||||
-- _initVisualState, so no `props` reference is needed here (the hook
|
||||
-- signature is `(element)`).
|
||||
element._textEditor = Element._TextEditor.new({
|
||||
editable = element.editable,
|
||||
multiline = element.multiline,
|
||||
passwordMode = element.passwordMode,
|
||||
textWrap = element.textWrap,
|
||||
maxLines = element.maxLines,
|
||||
maxLength = element.maxLength,
|
||||
placeholder = element.placeholder,
|
||||
inputType = element.inputType,
|
||||
textOverflow = element.textOverflow,
|
||||
scrollable = element.scrollable,
|
||||
autoGrow = element.autoGrow,
|
||||
selectOnFocus = element.selectOnFocus,
|
||||
cursorColor = element.cursorColor,
|
||||
selectionColor = element.selectionColor,
|
||||
cursorBlinkRate = element.cursorBlinkRate,
|
||||
text = element.text or "",
|
||||
onFocus = element.onFocus,
|
||||
onBlur = element.onBlur,
|
||||
onTextInput = element.onTextInput,
|
||||
onTextChange = element.onTextChange,
|
||||
onEnter = element.onEnter,
|
||||
}, Element._textEditorDeps)
|
||||
|
||||
-- Restore TextEditor state from StateManager in immediate mode. Mirrors the
|
||||
-- legacy _initSubSystems immediate-mode restore. Safe to run here (after
|
||||
-- _construct registered the element with StateManager) — the StateManager
|
||||
-- lookup is sparse and returns nil for a fresh element. Mode-aware via
|
||||
-- Context.isImmediateMode (behavior-mode-unification task 11).
|
||||
if Element._Context.isImmediateMode() and element._stateId and element._stateId ~= "" then
|
||||
local state = Element._StateManager.getState(element._stateId)
|
||||
if state and state.textEditor then
|
||||
element._textEditor:setState(state.textEditor, element)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function onDetach(element)
|
||||
-- Clear text-input callback closures read by TextEditor / KeyboardNavigation
|
||||
-- so the element's closure references can be collected in immediate mode
|
||||
-- (formerly part of Element:_cleanup). The TextEditor instance itself is
|
||||
-- INTENTIONALLY kept: Element:_cleanup preserves element structure for
|
||||
-- inspection (released when the element is GC'd).
|
||||
element.onTextInput = nil
|
||||
element.onTextChange = nil
|
||||
element.onEnter = nil
|
||||
end
|
||||
|
||||
-- ============================================================================
|
||||
-- onUpdate — cursor-blink animation (formerly Element:update line ~2810).
|
||||
-- ============================================================================
|
||||
|
||||
-- Drives TextEditor:update (cursor blink + blink-pause timer). Guarded on
|
||||
-- `element._textEditor` because non-editable text elements attach this
|
||||
-- behavior (per shouldAttach) but own no TextEditor. Element:update contains
|
||||
-- zero text-editor references — the dispatch loop calls this hook.
|
||||
local function onUpdate(element, dt)
|
||||
local textEditor = element._textEditor
|
||||
if textEditor then
|
||||
textEditor:update(element, dt)
|
||||
end
|
||||
end
|
||||
|
||||
-- ============================================================================
|
||||
-- onDraw — no-op (see file header: text rendering stays in the Renderer
|
||||
-- command buffer driven by the Thamed behavior's Renderer:draw call).
|
||||
-- ============================================================================
|
||||
|
||||
local function onDraw() end
|
||||
|
||||
-- ============================================================================
|
||||
-- saveState / restoreState — TextEditor state + text-selection drag
|
||||
-- tracking (formerly the textEditor branch of Element:saveState /
|
||||
-- Element:restoreState, including the _mouseDownPosition / _textDragOccurred
|
||||
-- fields).
|
||||
-- ============================================================================
|
||||
|
||||
-- Returns a snapshot under the `textEditor` key to match the legacy immediate-
|
||||
-- mode restoreState contract (Element:restoreState looked up state.textEditor).
|
||||
-- The behavior-dispatch loop in Element:saveState merges behavior snapshots
|
||||
-- into the top-level state table, so returning { textEditor = ... } slots in
|
||||
-- identically to the old inline `state.textEditor = self._textEditor:getState()`
|
||||
-- assignment. The drag-tracking fields are merged at the top level too
|
||||
-- (matching the legacy `state._mouseDownPosition` / `state._textDragOccurred`
|
||||
-- assignments) since they are text-selection state.
|
||||
local function saveState(element)
|
||||
local textEditor = element._textEditor
|
||||
if not textEditor then
|
||||
-- Non-editable text element: still persist drag-tracking fields if set
|
||||
-- (they are only ever set for editable elements, but persist defensively).
|
||||
local hasDragState = element._mouseDownPosition ~= nil or element._textDragOccurred ~= nil
|
||||
if not hasDragState then
|
||||
return nil
|
||||
end
|
||||
local snapshot = {}
|
||||
if element._mouseDownPosition ~= nil then
|
||||
snapshot._mouseDownPosition = element._mouseDownPosition
|
||||
end
|
||||
if element._textDragOccurred ~= nil then
|
||||
snapshot._textDragOccurred = element._textDragOccurred
|
||||
end
|
||||
return snapshot
|
||||
end
|
||||
|
||||
local snapshot = { textEditor = textEditor:getState() }
|
||||
if element._mouseDownPosition ~= nil then
|
||||
snapshot._mouseDownPosition = element._mouseDownPosition
|
||||
end
|
||||
if element._textDragOccurred ~= nil then
|
||||
snapshot._textDragOccurred = element._textDragOccurred
|
||||
end
|
||||
return snapshot
|
||||
end
|
||||
|
||||
-- Consumes the previously-saved snapshot keyed under `textEditor` plus the
|
||||
-- drag-tracking fields. The behavior-dispatch loop passes the FULL top-level
|
||||
-- state table; this hook reads only its own slices, mirroring the legacy
|
||||
-- `if self._textEditor and state.textEditor then ... end` guard.
|
||||
local function restoreState(element, state)
|
||||
if not state then
|
||||
return
|
||||
end
|
||||
local textEditor = element._textEditor
|
||||
if textEditor and state.textEditor then
|
||||
textEditor:setState(state.textEditor, element)
|
||||
-- Sync TextEditor's focus/cursor/selection state to Element for theme
|
||||
-- management (mirrors the legacy restoreState field sync).
|
||||
element._focused = textEditor._focused
|
||||
element._cursorPosition = textEditor._cursorPosition
|
||||
element._selectionStart = textEditor._selectionStart
|
||||
element._selectionEnd = textEditor._selectionEnd
|
||||
element._textBuffer = textEditor._textBuffer
|
||||
end
|
||||
|
||||
-- Restore drag-tracking state for text selection (top-level keys).
|
||||
if state._mouseDownPosition ~= nil then
|
||||
element._mouseDownPosition = state._mouseDownPosition
|
||||
end
|
||||
if state._textDragOccurred ~= nil then
|
||||
element._textDragOccurred = state._textDragOccurred
|
||||
end
|
||||
end
|
||||
|
||||
-- ============================================================================
|
||||
-- Text-editor delegate functions.
|
||||
--
|
||||
-- These are the module-level implementations of the 27 text-editor delegate
|
||||
-- methods that previously lived on Element. Each mirrors the pre-refactor
|
||||
-- Element method body VERBATIM (with `self` → `element`), including the
|
||||
-- `element._textEditor` nil-guard: the guard is required because (a) non-
|
||||
-- editable text elements attach this behavior (per shouldAttach) but own no
|
||||
-- TextEditor, and (b) Element forwards these methods BEFORE onAttach has run
|
||||
-- (e.g. an `onCreate` callback firing during _finalizeConstruction, which
|
||||
-- runs before _attachBehaviors). The nil-guards live in THIS file (not in
|
||||
-- Element.lua), so the Element.lua `if self._textEditor` count drops to 0.
|
||||
--
|
||||
-- Element retains 1-line forwarders: `Element.setText = function(self, text)
|
||||
-- return Element._TextEditable.setText(self, text) end` (etc.), so external
|
||||
-- callers (EventHandler, KeyboardNavigation, game UI) keep working unchanged.
|
||||
--
|
||||
-- The TextEditor API is mixed: most methods take the element as first arg
|
||||
-- (`te:method(element, ...)` — "passesSelf"); a few getters omit it
|
||||
-- (`te:method()`). The delegation contract is pinned by
|
||||
-- subsystem_delegation_test.lua, so this mapping must match TextEditor's
|
||||
-- method signatures exactly.
|
||||
-- ============================================================================
|
||||
|
||||
-- --- Cursor management (passesSelf = element forwarded) ------------------
|
||||
|
||||
local function setCursorPosition(element, position)
|
||||
local textEditor = element._textEditor
|
||||
if textEditor then
|
||||
textEditor:setCursorPosition(element, position)
|
||||
end
|
||||
end
|
||||
|
||||
local function getCursorPosition(element)
|
||||
local textEditor = element._textEditor
|
||||
if textEditor then
|
||||
return textEditor:getCursorPosition()
|
||||
end
|
||||
return 0
|
||||
end
|
||||
|
||||
local function moveCursorBy(element, delta)
|
||||
local textEditor = element._textEditor
|
||||
if textEditor then
|
||||
textEditor:moveCursorBy(element, delta)
|
||||
end
|
||||
end
|
||||
|
||||
local function moveCursorToStart(element)
|
||||
local textEditor = element._textEditor
|
||||
if textEditor then
|
||||
textEditor:moveCursorToStart(element)
|
||||
end
|
||||
end
|
||||
|
||||
local function moveCursorToEnd(element)
|
||||
local textEditor = element._textEditor
|
||||
if textEditor then
|
||||
textEditor:moveCursorToEnd(element)
|
||||
end
|
||||
end
|
||||
|
||||
local function moveCursorToLineStart(element)
|
||||
local textEditor = element._textEditor
|
||||
if textEditor then
|
||||
textEditor:moveCursorToLineStart(element)
|
||||
end
|
||||
end
|
||||
|
||||
local function moveCursorToLineEnd(element)
|
||||
local textEditor = element._textEditor
|
||||
if textEditor then
|
||||
textEditor:moveCursorToLineEnd(element)
|
||||
end
|
||||
end
|
||||
|
||||
local function moveCursorToPreviousWord(element)
|
||||
local textEditor = element._textEditor
|
||||
if textEditor then
|
||||
textEditor:moveCursorToPreviousWord(element)
|
||||
end
|
||||
end
|
||||
|
||||
local function moveCursorToNextWord(element)
|
||||
local textEditor = element._textEditor
|
||||
if textEditor then
|
||||
textEditor:moveCursorToNextWord(element)
|
||||
end
|
||||
end
|
||||
|
||||
-- --- Selection management ------------------------------------------------
|
||||
|
||||
local function setSelection(element, startPos, endPos)
|
||||
local textEditor = element._textEditor
|
||||
if textEditor then
|
||||
textEditor:setSelection(element, startPos, endPos)
|
||||
end
|
||||
end
|
||||
|
||||
local function getSelection(element)
|
||||
local textEditor = element._textEditor
|
||||
if textEditor then
|
||||
return textEditor:getSelection()
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
local function hasSelection(element)
|
||||
local textEditor = element._textEditor
|
||||
if textEditor ~= nil then
|
||||
return textEditor:hasSelection()
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
local function clearSelection(element)
|
||||
local textEditor = element._textEditor
|
||||
if textEditor then
|
||||
textEditor:clearSelection(element)
|
||||
end
|
||||
end
|
||||
|
||||
local function selectAll(element)
|
||||
local textEditor = element._textEditor
|
||||
if textEditor then
|
||||
textEditor:selectAll(element)
|
||||
end
|
||||
end
|
||||
|
||||
local function getSelectedText(element)
|
||||
local textEditor = element._textEditor
|
||||
if textEditor then
|
||||
return textEditor:getSelectedText()
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
local function deleteSelection(element)
|
||||
local textEditor = element._textEditor
|
||||
if textEditor then
|
||||
return textEditor:deleteSelection(element)
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
-- --- Focus management ----------------------------------------------------
|
||||
|
||||
local function focus(element)
|
||||
local textEditor = element._textEditor
|
||||
if textEditor then
|
||||
textEditor:focus(element)
|
||||
end
|
||||
end
|
||||
|
||||
local function blur(element)
|
||||
local textEditor = element._textEditor
|
||||
if textEditor then
|
||||
textEditor:blur(element)
|
||||
end
|
||||
end
|
||||
|
||||
local function isFocused(element)
|
||||
local textEditor = element._textEditor
|
||||
if textEditor then
|
||||
return textEditor:isFocused()
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
-- --- Text buffer management (with post-delegation sync) ------------------
|
||||
-- These methods sync `element.text` from the TextEditor result + drive
|
||||
-- auto-grow, exactly as the legacy Element methods did.
|
||||
|
||||
local function getText(element)
|
||||
local textEditor = element._textEditor
|
||||
if textEditor then
|
||||
return textEditor:getText()
|
||||
end
|
||||
return element.text or ""
|
||||
end
|
||||
|
||||
local function setText(element, text)
|
||||
local textEditor = element._textEditor
|
||||
if textEditor then
|
||||
textEditor:setText(element, text)
|
||||
element.text = textEditor:getText() -- Sync display text
|
||||
textEditor:updateAutoGrowHeight(element)
|
||||
return
|
||||
end
|
||||
element.text = text
|
||||
end
|
||||
|
||||
local function insertText(element, text, position)
|
||||
local textEditor = element._textEditor
|
||||
if textEditor then
|
||||
textEditor:insertText(element, text, position)
|
||||
element.text = textEditor:getText() -- Sync display text
|
||||
textEditor:updateAutoGrowHeight(element)
|
||||
end
|
||||
end
|
||||
|
||||
local function deleteText(element, startPos, endPos)
|
||||
local textEditor = element._textEditor
|
||||
if textEditor then
|
||||
textEditor:deleteText(element, startPos, endPos)
|
||||
element.text = textEditor:getText() -- Sync display text
|
||||
textEditor:updateAutoGrowHeight(element)
|
||||
end
|
||||
end
|
||||
|
||||
local function replaceText(element, startPos, endPos, newText)
|
||||
local textEditor = element._textEditor
|
||||
if textEditor then
|
||||
textEditor:replaceText(element, startPos, endPos, newText)
|
||||
element.text = textEditor:getText() -- Sync display text
|
||||
textEditor:updateAutoGrowHeight(element)
|
||||
end
|
||||
end
|
||||
|
||||
-- --- Mouse text selection ------------------------------------------------
|
||||
|
||||
local function handleTextClick(element, mouseX, mouseY, clickCount)
|
||||
local textEditor = element._textEditor
|
||||
if textEditor then
|
||||
textEditor:handleTextClick(element, mouseX, mouseY, clickCount)
|
||||
-- Store mouse down position on element for drag tracking
|
||||
if clickCount == 1 then
|
||||
element._mouseDownPosition = textEditor:mouseToTextPosition(element, mouseX, mouseY)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function handleTextDrag(element, mouseX, mouseY)
|
||||
local textEditor = element._textEditor
|
||||
if textEditor then
|
||||
textEditor:handleTextDrag(element, mouseX, mouseY)
|
||||
element._textDragOccurred = textEditor._textDragOccurred
|
||||
end
|
||||
end
|
||||
|
||||
-- --- Keyboard input ------------------------------------------------------
|
||||
|
||||
local function textinput(element, text)
|
||||
local textEditor = element._textEditor
|
||||
if textEditor then
|
||||
textEditor:handleTextInput(element, text)
|
||||
element.text = textEditor:getText() -- Sync display text
|
||||
textEditor:updateAutoGrowHeight(element)
|
||||
end
|
||||
end
|
||||
|
||||
local function keypressed(element, key, scancode, isrepeat)
|
||||
local textEditor = element._textEditor
|
||||
if textEditor then
|
||||
textEditor:handleKeyPress(element, key, scancode, isrepeat)
|
||||
element.text = textEditor:getText() -- Sync display text
|
||||
textEditor:updateAutoGrowHeight(element)
|
||||
end
|
||||
end
|
||||
|
||||
-- ============================================================================
|
||||
-- Build the (stateless, shared, immutable) behavior instance + thin module
|
||||
-- table exposing the delegate functions (mirrors the Animated pattern).
|
||||
-- ============================================================================
|
||||
|
||||
local behavior = Behavior.new({
|
||||
onAttach = onAttach,
|
||||
onDetach = onDetach,
|
||||
onUpdate = onUpdate,
|
||||
onDraw = onDraw,
|
||||
saveState = saveState,
|
||||
restoreState = restoreState,
|
||||
shouldAttach = shouldAttach,
|
||||
})
|
||||
|
||||
-- Thin module table: exposes the frozen behavior instance (for the registry)
|
||||
-- plus the text-editor delegate functions (for Element's 1-line forwarders).
|
||||
-- All hooks delegate to the frozen behavior instance so dispatch sites get
|
||||
-- the validated, frozen implementation. shouldAttach is also exposed at module
|
||||
-- level (mirrors Clickable.shouldAttach) for tests/callers without an element.
|
||||
local TextEditable = {
|
||||
behavior = behavior,
|
||||
shouldAttach = shouldAttach,
|
||||
onAttach = onAttach,
|
||||
onUpdate = onUpdate,
|
||||
onDraw = onDraw,
|
||||
saveState = saveState,
|
||||
restoreState = restoreState,
|
||||
-- Text-editor delegate functions (Element forwarders route through these):
|
||||
setCursorPosition = setCursorPosition,
|
||||
getCursorPosition = getCursorPosition,
|
||||
moveCursorBy = moveCursorBy,
|
||||
moveCursorToStart = moveCursorToStart,
|
||||
moveCursorToEnd = moveCursorToEnd,
|
||||
moveCursorToLineStart = moveCursorToLineStart,
|
||||
moveCursorToLineEnd = moveCursorToLineEnd,
|
||||
moveCursorToPreviousWord = moveCursorToPreviousWord,
|
||||
moveCursorToNextWord = moveCursorToNextWord,
|
||||
setSelection = setSelection,
|
||||
getSelection = getSelection,
|
||||
hasSelection = hasSelection,
|
||||
clearSelection = clearSelection,
|
||||
selectAll = selectAll,
|
||||
getSelectedText = getSelectedText,
|
||||
deleteSelection = deleteSelection,
|
||||
focus = focus,
|
||||
blur = blur,
|
||||
isFocused = isFocused,
|
||||
getText = getText,
|
||||
setText = setText,
|
||||
insertText = insertText,
|
||||
deleteText = deleteText,
|
||||
replaceText = replaceText,
|
||||
_handleTextClick = handleTextClick,
|
||||
_handleTextDrag = handleTextDrag,
|
||||
textinput = textinput,
|
||||
keypressed = keypressed,
|
||||
}
|
||||
|
||||
-- Metatable so the module table itself satisfies the duck-typed registry
|
||||
-- contract (iterating `Element._behaviorRegistry` calls `behavior.shouldAttach`
|
||||
-- and `behavior.onAttach` / `behavior.onUpdate` directly). Falls through to the
|
||||
-- frozen behavior instance for every hook / isBehavior parity.
|
||||
setmetatable(TextEditable, {
|
||||
__index = behavior,
|
||||
__tostring = function()
|
||||
return "TextEditable"
|
||||
end,
|
||||
})
|
||||
|
||||
return TextEditable
|
||||
Reference in New Issue
Block a user