@rlabs-inc/tui 0.6.0 → 0.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +70 -65
- package/index.ts +3 -1
- package/package.json +1 -1
- package/src/primitives/box.ts +53 -1
- package/src/primitives/index.ts +1 -1
- package/src/primitives/input.ts +19 -0
- package/src/primitives/text.ts +19 -0
- package/src/primitives/types.ts +30 -3
package/README.md
CHANGED
|
@@ -16,26 +16,24 @@ A blazing-fast, fine-grained reactive terminal UI framework with complete flexbo
|
|
|
16
16
|
## Quick Start
|
|
17
17
|
|
|
18
18
|
```bash
|
|
19
|
-
bun add
|
|
19
|
+
bun add @rlabs-inc/tui
|
|
20
20
|
```
|
|
21
21
|
|
|
22
22
|
```typescript
|
|
23
|
-
import { mount, box, text, derived } from 'tui'
|
|
24
|
-
import { signal } from '@rlabs-inc/signals'
|
|
23
|
+
import { mount, box, text, signal, derived } from '@rlabs-inc/tui'
|
|
25
24
|
|
|
26
25
|
// Create reactive state
|
|
27
26
|
const count = signal(0)
|
|
28
27
|
const doubled = derived(() => count.value * 2)
|
|
29
28
|
|
|
30
|
-
//
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
}})
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
mount()
|
|
29
|
+
// Mount to terminal - build UI inside mount()
|
|
30
|
+
mount(() => {
|
|
31
|
+
box({ width: 40, height: 10, children: () => {
|
|
32
|
+
text({ content: count }) // signal directly
|
|
33
|
+
text({ content: doubled }) // derived directly
|
|
34
|
+
text({ content: () => `Count: ${count.value}` }) // () => for formatting
|
|
35
|
+
}})
|
|
36
|
+
})
|
|
39
37
|
|
|
40
38
|
// Update anywhere - UI reacts automatically
|
|
41
39
|
setInterval(() => count.value++, 1000)
|
|
@@ -85,12 +83,13 @@ User Signals → bind() → Parallel Arrays → layoutDerived → frameBufferDer
|
|
|
85
83
|
|
|
86
84
|
```bash
|
|
87
85
|
# Run examples
|
|
88
|
-
bun run examples/hello.ts
|
|
89
|
-
bun run examples/showcase.ts
|
|
86
|
+
bun run examples/showcase/01-hello-counter.ts
|
|
87
|
+
bun run examples/showcase/08-todo-app.ts
|
|
88
|
+
bun run examples/showcase/04-dashboard.ts
|
|
90
89
|
|
|
91
|
-
# Run tests
|
|
92
|
-
bun run examples/tests/01-box-basics.ts
|
|
90
|
+
# Run visual tests
|
|
93
91
|
bun run examples/tests/03-layout-flex.ts
|
|
92
|
+
bun run examples/tests/05-flex-complete.ts
|
|
94
93
|
```
|
|
95
94
|
|
|
96
95
|
## Documentation
|
|
@@ -102,7 +101,7 @@ bun run examples/tests/03-layout-flex.ts
|
|
|
102
101
|
- [First App Tutorial](./docs/getting-started/first-app.md) - Build a complete app
|
|
103
102
|
|
|
104
103
|
### User Guides
|
|
105
|
-
- [Primitives](./docs/guides/primitives/) - box, text, each, show, when
|
|
104
|
+
- [Primitives](./docs/guides/primitives/box.md) - box, text, each, show, when
|
|
106
105
|
- [Layout](./docs/guides/layout/flexbox.md) - Flexbox layout system
|
|
107
106
|
- [Styling](./docs/guides/styling/colors.md) - Colors, themes, borders
|
|
108
107
|
- [Reactivity](./docs/guides/reactivity/signals.md) - Signals and reactivity
|
|
@@ -125,7 +124,7 @@ bun run examples/tests/03-layout-flex.ts
|
|
|
125
124
|
|-----------|--------|-------------|
|
|
126
125
|
| `box` | Complete | Container with flexbox layout |
|
|
127
126
|
| `text` | Complete | Text display with styling |
|
|
128
|
-
| `input` |
|
|
127
|
+
| `input` | Complete | Text input field |
|
|
129
128
|
| `select` | Planned | Dropdown selection |
|
|
130
129
|
| `progress` | Planned | Progress bar |
|
|
131
130
|
|
|
@@ -144,26 +143,28 @@ Reactive control flow for dynamic UIs - no manual effects needed!
|
|
|
144
143
|
Renders a list of components that automatically updates when the array changes.
|
|
145
144
|
|
|
146
145
|
```typescript
|
|
147
|
-
import { each, box, text
|
|
146
|
+
import { mount, signal, each, box, text } from '@rlabs-inc/tui'
|
|
148
147
|
|
|
149
148
|
const todos = signal([
|
|
150
149
|
{ id: '1', text: 'Learn TUI', done: false },
|
|
151
150
|
{ id: '2', text: 'Build app', done: false },
|
|
152
151
|
])
|
|
153
152
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
(
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
153
|
+
mount(() => {
|
|
154
|
+
box({
|
|
155
|
+
children: () => {
|
|
156
|
+
each(
|
|
157
|
+
() => todos.value, // Reactive array getter
|
|
158
|
+
(getItem, key) => box({ // Render function: getItem() + stable key
|
|
159
|
+
id: `todo-${key}`, // Use key for stable ID
|
|
160
|
+
children: () => {
|
|
161
|
+
text({ content: () => getItem().text }) // Call getItem() to access value
|
|
162
|
+
}
|
|
163
|
+
}),
|
|
164
|
+
{ key: (todo) => todo.id } // Key function for efficient updates
|
|
165
|
+
)
|
|
166
|
+
}
|
|
167
|
+
})
|
|
167
168
|
})
|
|
168
169
|
|
|
169
170
|
// Add item - UI updates automatically
|
|
@@ -178,22 +179,24 @@ todos.value = todos.value.filter(t => t.id !== '1')
|
|
|
178
179
|
Shows or hides components based on a reactive condition.
|
|
179
180
|
|
|
180
181
|
```typescript
|
|
181
|
-
import { show, box, text, signal } from '@rlabs-inc/tui'
|
|
182
|
+
import { mount, show, box, text, signal } from '@rlabs-inc/tui'
|
|
182
183
|
|
|
183
184
|
const isLoggedIn = signal(false)
|
|
184
185
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
(
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
186
|
+
mount(() => {
|
|
187
|
+
box({
|
|
188
|
+
children: () => {
|
|
189
|
+
show(
|
|
190
|
+
() => isLoggedIn.value, // Condition getter
|
|
191
|
+
() => box({ // Render when true
|
|
192
|
+
children: () => {
|
|
193
|
+
text({ content: 'Welcome back!' })
|
|
194
|
+
}
|
|
195
|
+
}),
|
|
196
|
+
() => text({ content: 'Please log in' }) // Optional: render when false
|
|
197
|
+
)
|
|
198
|
+
}
|
|
199
|
+
})
|
|
197
200
|
})
|
|
198
201
|
|
|
199
202
|
// Toggle - UI switches automatically
|
|
@@ -205,7 +208,7 @@ isLoggedIn.value = true
|
|
|
205
208
|
Handles async operations with loading, success, and error states.
|
|
206
209
|
|
|
207
210
|
```typescript
|
|
208
|
-
import { when, box, text, signal } from '@rlabs-inc/tui'
|
|
211
|
+
import { mount, when, box, text, signal } from '@rlabs-inc/tui'
|
|
209
212
|
|
|
210
213
|
const userId = signal('123')
|
|
211
214
|
|
|
@@ -213,25 +216,27 @@ const userId = signal('123')
|
|
|
213
216
|
const fetchUser = (id: string) =>
|
|
214
217
|
fetch(`/api/users/${id}`).then(r => r.json())
|
|
215
218
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
(
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
219
|
+
mount(() => {
|
|
220
|
+
box({
|
|
221
|
+
children: () => {
|
|
222
|
+
when(
|
|
223
|
+
() => fetchUser(userId.value), // Promise getter (re-runs on userId change)
|
|
224
|
+
{
|
|
225
|
+
pending: () => text({ content: 'Loading...' }),
|
|
226
|
+
then: (user) => box({
|
|
227
|
+
children: () => {
|
|
228
|
+
text({ content: `Name: ${user.name}` })
|
|
229
|
+
text({ content: `Email: ${user.email}` })
|
|
230
|
+
}
|
|
231
|
+
}),
|
|
232
|
+
catch: (error) => text({
|
|
233
|
+
content: `Error: ${error.message}`,
|
|
234
|
+
fg: 'red'
|
|
235
|
+
})
|
|
236
|
+
}
|
|
237
|
+
)
|
|
238
|
+
}
|
|
239
|
+
})
|
|
235
240
|
})
|
|
236
241
|
|
|
237
242
|
// Change userId - triggers new fetch, shows loading, then result
|
package/index.ts
CHANGED
|
@@ -10,7 +10,7 @@ export { mount } from './src/api'
|
|
|
10
10
|
|
|
11
11
|
// Primitives - UI building blocks
|
|
12
12
|
export { box, text, input, each, show, when, scoped, onCleanup, useAnimation, AnimationFrames } from './src/primitives'
|
|
13
|
-
export type { BoxProps, TextProps, InputProps, CursorConfig, CursorStyle, Cleanup, AnimationOptions } from './src/primitives'
|
|
13
|
+
export type { BoxProps, TextProps, InputProps, CursorConfig, CursorStyle, Cleanup, AnimationOptions, MouseProps } from './src/primitives'
|
|
14
14
|
|
|
15
15
|
// Lifecycle hooks - Component mount/destroy callbacks
|
|
16
16
|
export { onMount, onDestroy } from './src/engine/lifecycle'
|
|
@@ -21,6 +21,7 @@ export type { Context } from './src/state/context'
|
|
|
21
21
|
|
|
22
22
|
// State modules - Input handling
|
|
23
23
|
export { keyboard, lastKey, lastEvent } from './src/state/keyboard'
|
|
24
|
+
export type { KeyHandler, KeyboardEvent as TuiKeyboardEvent, Modifiers } from './src/state/keyboard'
|
|
24
25
|
export {
|
|
25
26
|
mouse,
|
|
26
27
|
hitGrid,
|
|
@@ -34,6 +35,7 @@ export {
|
|
|
34
35
|
onScroll,
|
|
35
36
|
onComponent,
|
|
36
37
|
} from './src/state/mouse'
|
|
38
|
+
export type { MouseEvent as TuiMouseEvent, MouseHandler, MouseHandlers } from './src/state/mouse'
|
|
37
39
|
export { focusManager, focusedIndex, pushFocusTrap, popFocusTrap, isFocusTrapped, getFocusTrapContainer } from './src/state/focus'
|
|
38
40
|
export { scroll } from './src/state/scroll'
|
|
39
41
|
export { globalKeys } from './src/state/global-keys'
|
package/package.json
CHANGED
package/src/primitives/box.ts
CHANGED
|
@@ -35,7 +35,9 @@ import {
|
|
|
35
35
|
popCurrentComponent,
|
|
36
36
|
runMountCallbacks,
|
|
37
37
|
} from '../engine/lifecycle'
|
|
38
|
-
import { cleanupIndex as cleanupKeyboardListeners } from '../state/keyboard'
|
|
38
|
+
import { cleanupIndex as cleanupKeyboardListeners, onFocused } from '../state/keyboard'
|
|
39
|
+
import { registerFocusCallbacks, focus as focusComponent } from '../state/focus'
|
|
40
|
+
import { onComponent as onMouseComponent } from '../state/mouse'
|
|
39
41
|
import { getVariantStyle } from '../state/theme'
|
|
40
42
|
import { getActiveScope } from './scope'
|
|
41
43
|
import { enumSource } from './utils'
|
|
@@ -232,6 +234,53 @@ export function box(props: BoxProps = {}): Cleanup {
|
|
|
232
234
|
if (props.tabIndex !== undefined) interaction.tabIndex.setSource(index, props.tabIndex)
|
|
233
235
|
}
|
|
234
236
|
|
|
237
|
+
// ==========================================================================
|
|
238
|
+
// FOCUS CALLBACKS & KEYBOARD HANDLER
|
|
239
|
+
// Only registered when focusable AND callbacks provided
|
|
240
|
+
// ==========================================================================
|
|
241
|
+
let unsubKeyboard: (() => void) | undefined
|
|
242
|
+
let unsubFocusCallbacks: (() => void) | undefined
|
|
243
|
+
|
|
244
|
+
if (shouldBeFocusable) {
|
|
245
|
+
// Register keyboard handler (fires only when this box has focus)
|
|
246
|
+
if (props.onKey) {
|
|
247
|
+
unsubKeyboard = onFocused(index, props.onKey)
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// Register focus/blur callbacks (fires at the source - focus manager)
|
|
251
|
+
if (props.onFocus || props.onBlur) {
|
|
252
|
+
unsubFocusCallbacks = registerFocusCallbacks(index, {
|
|
253
|
+
onFocus: props.onFocus,
|
|
254
|
+
onBlur: props.onBlur,
|
|
255
|
+
})
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// ==========================================================================
|
|
260
|
+
// MOUSE HANDLERS
|
|
261
|
+
// Registered when focusable (for click-to-focus) OR any mouse callback provided
|
|
262
|
+
// ==========================================================================
|
|
263
|
+
let unsubMouse: (() => void) | undefined
|
|
264
|
+
|
|
265
|
+
const hasMouseHandlers = props.onMouseDown || props.onMouseUp || props.onClick || props.onMouseEnter || props.onMouseLeave || props.onScroll
|
|
266
|
+
|
|
267
|
+
if (shouldBeFocusable || hasMouseHandlers) {
|
|
268
|
+
unsubMouse = onMouseComponent(index, {
|
|
269
|
+
onMouseDown: props.onMouseDown,
|
|
270
|
+
onMouseUp: props.onMouseUp,
|
|
271
|
+
// Click-to-focus: focus this component on click (if focusable), then call user's handler
|
|
272
|
+
onClick: (event) => {
|
|
273
|
+
if (shouldBeFocusable) {
|
|
274
|
+
focusComponent(index)
|
|
275
|
+
}
|
|
276
|
+
return props.onClick?.(event)
|
|
277
|
+
},
|
|
278
|
+
onMouseEnter: props.onMouseEnter,
|
|
279
|
+
onMouseLeave: props.onMouseLeave,
|
|
280
|
+
onScroll: props.onScroll,
|
|
281
|
+
})
|
|
282
|
+
}
|
|
283
|
+
|
|
235
284
|
// ==========================================================================
|
|
236
285
|
// VISUAL - Colors and borders (only bind what's passed)
|
|
237
286
|
// ==========================================================================
|
|
@@ -284,6 +333,9 @@ export function box(props: BoxProps = {}): Cleanup {
|
|
|
284
333
|
|
|
285
334
|
// Cleanup function
|
|
286
335
|
const cleanup = () => {
|
|
336
|
+
unsubFocusCallbacks?.()
|
|
337
|
+
unsubMouse?.()
|
|
338
|
+
unsubKeyboard?.()
|
|
287
339
|
cleanupKeyboardListeners(index) // Remove any focused key handlers
|
|
288
340
|
releaseIndex(index)
|
|
289
341
|
}
|
package/src/primitives/index.ts
CHANGED
|
@@ -15,6 +15,6 @@ export { scoped, onCleanup, componentScope, cleanupCollector } from './scope'
|
|
|
15
15
|
export { useAnimation, AnimationFrames } from './animation'
|
|
16
16
|
|
|
17
17
|
// Types
|
|
18
|
-
export type { BoxProps, TextProps, InputProps, CursorConfig, CursorStyle, BlinkConfig, Cleanup } from './types'
|
|
18
|
+
export type { BoxProps, TextProps, InputProps, CursorConfig, CursorStyle, BlinkConfig, Cleanup, MouseProps } from './types'
|
|
19
19
|
export type { ComponentScopeResult } from './scope'
|
|
20
20
|
export type { AnimationOptions } from './animation'
|
package/src/primitives/input.ts
CHANGED
|
@@ -32,6 +32,7 @@ import {
|
|
|
32
32
|
runMountCallbacks,
|
|
33
33
|
} from '../engine/lifecycle'
|
|
34
34
|
import { cleanupIndex as cleanupKeyboardListeners, onFocused } from '../state/keyboard'
|
|
35
|
+
import { onComponent as onMouseComponent } from '../state/mouse'
|
|
35
36
|
import { getVariantStyle, t } from '../state/theme'
|
|
36
37
|
import { focus as focusComponent, registerFocusCallbacks } from '../state/focus'
|
|
37
38
|
import { createCursor, disposeCursor } from '../state/drawnCursor'
|
|
@@ -322,6 +323,23 @@ export function input(props: InputProps): Cleanup {
|
|
|
322
323
|
onBlur: props.onBlur,
|
|
323
324
|
})
|
|
324
325
|
|
|
326
|
+
// ==========================================================================
|
|
327
|
+
// MOUSE HANDLERS
|
|
328
|
+
// Always registered for inputs (click-to-focus) + any user callbacks
|
|
329
|
+
// ==========================================================================
|
|
330
|
+
const unsubMouse = onMouseComponent(index, {
|
|
331
|
+
onMouseDown: props.onMouseDown,
|
|
332
|
+
onMouseUp: props.onMouseUp,
|
|
333
|
+
// Click-to-focus: inputs are always focusable, so click always focuses
|
|
334
|
+
onClick: (event) => {
|
|
335
|
+
focusComponent(index)
|
|
336
|
+
return props.onClick?.(event)
|
|
337
|
+
},
|
|
338
|
+
onMouseEnter: props.onMouseEnter,
|
|
339
|
+
onMouseLeave: props.onMouseLeave,
|
|
340
|
+
onScroll: props.onScroll,
|
|
341
|
+
})
|
|
342
|
+
|
|
325
343
|
// ==========================================================================
|
|
326
344
|
// AUTO FOCUS
|
|
327
345
|
// ==========================================================================
|
|
@@ -343,6 +361,7 @@ export function input(props: InputProps): Cleanup {
|
|
|
343
361
|
|
|
344
362
|
const cleanup = () => {
|
|
345
363
|
unsubFocusCallbacks()
|
|
364
|
+
unsubMouse()
|
|
346
365
|
cursor.dispose()
|
|
347
366
|
unsubKeyboard()
|
|
348
367
|
cleanupKeyboardListeners(index)
|
package/src/primitives/text.ts
CHANGED
|
@@ -32,6 +32,7 @@ import {
|
|
|
32
32
|
runMountCallbacks,
|
|
33
33
|
} from '../engine/lifecycle'
|
|
34
34
|
import { cleanupIndex as cleanupKeyboardListeners } from '../state/keyboard'
|
|
35
|
+
import { onComponent as onMouseComponent } from '../state/mouse'
|
|
35
36
|
import { getVariantStyle } from '../state/theme'
|
|
36
37
|
import { getActiveScope } from './scope'
|
|
37
38
|
import { enumSource } from './utils'
|
|
@@ -200,12 +201,30 @@ export function text(props: TextProps): Cleanup {
|
|
|
200
201
|
}
|
|
201
202
|
if (props.opacity !== undefined) visual.opacity.setSource(index, props.opacity)
|
|
202
203
|
|
|
204
|
+
// ==========================================================================
|
|
205
|
+
// MOUSE HANDLERS
|
|
206
|
+
// Registered when any mouse callback provided
|
|
207
|
+
// ==========================================================================
|
|
208
|
+
let unsubMouse: (() => void) | undefined
|
|
209
|
+
|
|
210
|
+
if (props.onMouseDown || props.onMouseUp || props.onClick || props.onMouseEnter || props.onMouseLeave || props.onScroll) {
|
|
211
|
+
unsubMouse = onMouseComponent(index, {
|
|
212
|
+
onMouseDown: props.onMouseDown,
|
|
213
|
+
onMouseUp: props.onMouseUp,
|
|
214
|
+
onClick: props.onClick,
|
|
215
|
+
onMouseEnter: props.onMouseEnter,
|
|
216
|
+
onMouseLeave: props.onMouseLeave,
|
|
217
|
+
onScroll: props.onScroll,
|
|
218
|
+
})
|
|
219
|
+
}
|
|
220
|
+
|
|
203
221
|
// Component setup complete - run lifecycle callbacks
|
|
204
222
|
popCurrentComponent()
|
|
205
223
|
runMountCallbacks(index)
|
|
206
224
|
|
|
207
225
|
// Cleanup function
|
|
208
226
|
const cleanup = () => {
|
|
227
|
+
unsubMouse?.()
|
|
209
228
|
cleanupKeyboardListeners(index)
|
|
210
229
|
releaseIndex(index)
|
|
211
230
|
}
|
package/src/primitives/types.ts
CHANGED
|
@@ -8,6 +8,8 @@
|
|
|
8
8
|
import type { RGBA, CellAttrs, Dimension } from '../types'
|
|
9
9
|
import type { WritableSignal, Binding, ReadonlyBinding } from '@rlabs-inc/signals'
|
|
10
10
|
import type { Variant } from '../state/theme'
|
|
11
|
+
import type { KeyHandler } from '../state/keyboard'
|
|
12
|
+
import type { MouseEvent, MouseHandler } from '../state/mouse'
|
|
11
13
|
|
|
12
14
|
// =============================================================================
|
|
13
15
|
// REACTIVE PROP TYPES
|
|
@@ -122,11 +124,26 @@ export interface InteractionProps {
|
|
|
122
124
|
tabIndex?: Reactive<number>
|
|
123
125
|
}
|
|
124
126
|
|
|
127
|
+
export interface MouseProps {
|
|
128
|
+
/** Called on mouse down over this component. Return true to consume event. */
|
|
129
|
+
onMouseDown?: (event: MouseEvent) => void | boolean
|
|
130
|
+
/** Called on mouse up over this component. Return true to consume event. */
|
|
131
|
+
onMouseUp?: (event: MouseEvent) => void | boolean
|
|
132
|
+
/** Called on click (down + up on same component). Return true to consume event. */
|
|
133
|
+
onClick?: (event: MouseEvent) => void | boolean
|
|
134
|
+
/** Called when mouse enters this component */
|
|
135
|
+
onMouseEnter?: (event: MouseEvent) => void
|
|
136
|
+
/** Called when mouse leaves this component */
|
|
137
|
+
onMouseLeave?: (event: MouseEvent) => void
|
|
138
|
+
/** Called on scroll over this component. Return true to consume event. */
|
|
139
|
+
onScroll?: (event: MouseEvent) => void | boolean
|
|
140
|
+
}
|
|
141
|
+
|
|
125
142
|
// =============================================================================
|
|
126
143
|
// BOX PROPS
|
|
127
144
|
// =============================================================================
|
|
128
145
|
|
|
129
|
-
export interface BoxProps extends StyleProps, BorderProps, DimensionProps, SpacingProps, LayoutProps, InteractionProps {
|
|
146
|
+
export interface BoxProps extends StyleProps, BorderProps, DimensionProps, SpacingProps, LayoutProps, InteractionProps, MouseProps {
|
|
130
147
|
/** Component ID (optional, auto-generated if not provided) */
|
|
131
148
|
id?: string
|
|
132
149
|
/** Is visible */
|
|
@@ -138,13 +155,23 @@ export interface BoxProps extends StyleProps, BorderProps, DimensionProps, Spaci
|
|
|
138
155
|
* Variants: 'default' | 'primary' | 'secondary' | 'success' | 'warning' | 'error' | 'info' | 'ghost' | 'outline'
|
|
139
156
|
*/
|
|
140
157
|
variant?: Variant
|
|
158
|
+
/**
|
|
159
|
+
* Keyboard handler - fires only when this box has focus.
|
|
160
|
+
* Return true to consume the event (prevent propagation).
|
|
161
|
+
* Requires focusable: true or overflow: 'scroll'.
|
|
162
|
+
*/
|
|
163
|
+
onKey?: KeyHandler
|
|
164
|
+
/** Called when this box receives focus */
|
|
165
|
+
onFocus?: () => void
|
|
166
|
+
/** Called when this box loses focus */
|
|
167
|
+
onBlur?: () => void
|
|
141
168
|
}
|
|
142
169
|
|
|
143
170
|
// =============================================================================
|
|
144
171
|
// TEXT PROPS
|
|
145
172
|
// =============================================================================
|
|
146
173
|
|
|
147
|
-
export interface TextProps extends StyleProps, DimensionProps, SpacingProps, LayoutProps {
|
|
174
|
+
export interface TextProps extends StyleProps, DimensionProps, SpacingProps, LayoutProps, MouseProps {
|
|
148
175
|
/** Component ID (optional, auto-generated if not provided) */
|
|
149
176
|
id?: string
|
|
150
177
|
/** Text content (strings and numbers auto-converted) */
|
|
@@ -192,7 +219,7 @@ export interface CursorConfig {
|
|
|
192
219
|
bg?: Reactive<RGBA>
|
|
193
220
|
}
|
|
194
221
|
|
|
195
|
-
export interface InputProps extends StyleProps, BorderProps, DimensionProps, SpacingProps, InteractionProps {
|
|
222
|
+
export interface InputProps extends StyleProps, BorderProps, DimensionProps, SpacingProps, InteractionProps, MouseProps {
|
|
196
223
|
/** Component ID (optional, auto-generated if not provided) */
|
|
197
224
|
id?: string
|
|
198
225
|
/** Current value (two-way bound) */
|