@askrjs/askr 0.0.5 → 0.0.7
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/dist/component-BBGWJdqJ.d.ts +177 -0
- package/dist/foundations/index.d.ts +373 -0
- package/dist/foundations/index.js +1051 -0
- package/dist/foundations/index.js.map +1 -0
- package/dist/{fx.js → fx/index.js} +2 -2
- package/dist/fx/index.js.map +1 -0
- package/dist/index.d.ts +4 -134
- package/dist/index.js +53 -68
- package/dist/index.js.map +1 -1
- package/dist/layout-BINPv-nz.d.ts +29 -0
- package/dist/{resources.d.ts → resources/index.d.ts} +3 -0
- package/dist/{resources.js → resources/index.js} +10 -3
- package/dist/resources/index.js.map +1 -0
- package/dist/{router.d.ts → router/index.d.ts} +4 -16
- package/dist/{router.js → router/index.js} +36 -4
- package/dist/router/index.js.map +1 -0
- package/dist/{ssr.d.ts → ssr/index.d.ts} +2 -2
- package/dist/{ssr.js → ssr/index.js} +62 -67
- package/dist/ssr/index.js.map +1 -0
- package/package.json +7 -5
- package/dist/fx.js.map +0 -1
- package/dist/resources.js.map +0 -1
- package/dist/router.js.map +0 -1
- package/dist/ssr.js.map +0 -1
- /package/dist/{fx.d.ts → fx/index.d.ts} +0 -0
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import { P as Props, J as JSXElement } from './jsx-AzPM8gMS.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* State primitive for Askr components
|
|
5
|
+
* Optimized for minimal overhead and fast updates
|
|
6
|
+
*
|
|
7
|
+
* INVARIANTS ENFORCED:
|
|
8
|
+
* - state() only callable during component render (currentInstance exists)
|
|
9
|
+
* - state() called at top-level only (indices must be monotonically increasing)
|
|
10
|
+
* - state values persist across re-renders (stored in stateValues array)
|
|
11
|
+
* - state.set() cannot be called during render (causes infinite loops)
|
|
12
|
+
* - state.set() always enqueues through scheduler (never direct mutation)
|
|
13
|
+
* - state.set() callback (notifyUpdate) always available
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* State value holder - callable to read, has set method to update
|
|
18
|
+
* @example
|
|
19
|
+
* const count = state(0);
|
|
20
|
+
* count(); // read: 0
|
|
21
|
+
* count.set(1); // write: triggers re-render
|
|
22
|
+
*/
|
|
23
|
+
interface State<T> {
|
|
24
|
+
(): T;
|
|
25
|
+
set(value: T): void;
|
|
26
|
+
set(updater: (prev: T) => T): void;
|
|
27
|
+
_hasBeenRead?: boolean;
|
|
28
|
+
_readers?: Map<ComponentInstance, number>;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Creates a local state value for a component
|
|
32
|
+
* Optimized for:
|
|
33
|
+
* - O(1) read performance
|
|
34
|
+
* - Minimal allocation per state
|
|
35
|
+
* - Fast scheduler integration
|
|
36
|
+
*
|
|
37
|
+
* IMPORTANT: state() must be called during component render execution.
|
|
38
|
+
* It captures the current component instance from context.
|
|
39
|
+
* Calling outside a component function will throw an error.
|
|
40
|
+
*
|
|
41
|
+
* @example
|
|
42
|
+
* ```ts
|
|
43
|
+
* // ✅ Correct: called during render
|
|
44
|
+
* export function Counter() {
|
|
45
|
+
* const count = state(0);
|
|
46
|
+
* return { type: 'button', children: [count()] };
|
|
47
|
+
* }
|
|
48
|
+
*
|
|
49
|
+
* // ❌ Wrong: called outside component
|
|
50
|
+
* const count = state(0);
|
|
51
|
+
* export function BadComponent() {
|
|
52
|
+
* return { type: 'div' };
|
|
53
|
+
* }
|
|
54
|
+
* ```
|
|
55
|
+
*/
|
|
56
|
+
declare function state<T>(initialValue: T): State<T>;
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Common call contracts: Component signatures
|
|
60
|
+
*/
|
|
61
|
+
|
|
62
|
+
type ComponentContext = {
|
|
63
|
+
signal: AbortSignal;
|
|
64
|
+
};
|
|
65
|
+
type ComponentVNode = {
|
|
66
|
+
type: string;
|
|
67
|
+
props?: Props;
|
|
68
|
+
children?: (string | ComponentVNode | null | undefined | false)[];
|
|
69
|
+
};
|
|
70
|
+
type ComponentFunction = (props: Props, context?: ComponentContext) => JSXElement | ComponentVNode | string | number | null;
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Context system: lexical scope + render-time snapshots
|
|
74
|
+
*
|
|
75
|
+
* CORE SEMANTIC (Option A — Snapshot-Based):
|
|
76
|
+
* ============================================
|
|
77
|
+
* An async resource observes the context of the render that created it.
|
|
78
|
+
* Context changes only take effect via re-render, not magically mid-await.
|
|
79
|
+
*
|
|
80
|
+
* This ensures:
|
|
81
|
+
* - Deterministic behavior
|
|
82
|
+
* - Concurrency safety
|
|
83
|
+
* - Replayable execution
|
|
84
|
+
* - Debuggability
|
|
85
|
+
*
|
|
86
|
+
* INVARIANTS:
|
|
87
|
+
* - readContext() only works during component render (has currentContextFrame)
|
|
88
|
+
* - Each render captures a context snapshot
|
|
89
|
+
* - Async continuations see the snapshot from render start (frozen)
|
|
90
|
+
* - Provider (Scope) creates a new frame that shadows parent
|
|
91
|
+
* - Context updates require re-render to take effect
|
|
92
|
+
*/
|
|
93
|
+
|
|
94
|
+
type ContextKey = symbol;
|
|
95
|
+
interface ContextFrame {
|
|
96
|
+
parent: ContextFrame | null;
|
|
97
|
+
values: Map<ContextKey, unknown> | null;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Component instance lifecycle management
|
|
102
|
+
* Internal only — users never see this
|
|
103
|
+
*/
|
|
104
|
+
|
|
105
|
+
interface ComponentInstance {
|
|
106
|
+
id: string;
|
|
107
|
+
fn: ComponentFunction;
|
|
108
|
+
props: Props;
|
|
109
|
+
target: Element | null;
|
|
110
|
+
mounted: boolean;
|
|
111
|
+
abortController: AbortController;
|
|
112
|
+
ssr?: boolean;
|
|
113
|
+
cleanupStrict?: boolean;
|
|
114
|
+
stateValues: State<unknown>[];
|
|
115
|
+
evaluationGeneration: number;
|
|
116
|
+
notifyUpdate: (() => void) | null;
|
|
117
|
+
_pendingFlushTask?: () => void;
|
|
118
|
+
_pendingRunTask?: () => void;
|
|
119
|
+
_enqueueRun?: () => void;
|
|
120
|
+
stateIndexCheck: number;
|
|
121
|
+
expectedStateIndices: number[];
|
|
122
|
+
firstRenderComplete: boolean;
|
|
123
|
+
mountOperations: Array<() => void | (() => void) | Promise<void | (() => void)>>;
|
|
124
|
+
cleanupFns: Array<() => void>;
|
|
125
|
+
hasPendingUpdate: boolean;
|
|
126
|
+
ownerFrame: ContextFrame | null;
|
|
127
|
+
isRoot?: boolean;
|
|
128
|
+
_currentRenderToken?: number;
|
|
129
|
+
lastRenderToken?: number;
|
|
130
|
+
_pendingReadStates?: Set<State<unknown>>;
|
|
131
|
+
_lastReadStates?: Set<State<unknown>>;
|
|
132
|
+
_placeholder?: Comment;
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Get the abort signal for the current component
|
|
136
|
+
* Used to cancel async operations on unmount/navigation
|
|
137
|
+
*
|
|
138
|
+
* The signal is guaranteed to be aborted when:
|
|
139
|
+
* - Component unmounts
|
|
140
|
+
* - Navigation occurs (different route)
|
|
141
|
+
* - Parent is destroyed
|
|
142
|
+
*
|
|
143
|
+
* IMPORTANT: getSignal() must be called during component render execution.
|
|
144
|
+
* It captures the current component instance from context.
|
|
145
|
+
*
|
|
146
|
+
* @returns AbortSignal that will be aborted when component unmounts
|
|
147
|
+
* @throws Error if called outside component execution
|
|
148
|
+
*
|
|
149
|
+
* @example
|
|
150
|
+
* ```ts
|
|
151
|
+
* // ✅ Correct: called during render, used in async operation
|
|
152
|
+
* export async function UserPage({ id }: { id: string }) {
|
|
153
|
+
* const signal = getSignal();
|
|
154
|
+
* const user = await fetch(`/api/users/${id}`, { signal });
|
|
155
|
+
* return <div>{user.name}</div>;
|
|
156
|
+
* }
|
|
157
|
+
*
|
|
158
|
+
* // ✅ Correct: passed to event handler
|
|
159
|
+
* export function Button() {
|
|
160
|
+
* const signal = getSignal();
|
|
161
|
+
* return {
|
|
162
|
+
* type: 'button',
|
|
163
|
+
* props: {
|
|
164
|
+
* onClick: async () => {
|
|
165
|
+
* const data = await fetch(url, { signal });
|
|
166
|
+
* }
|
|
167
|
+
* }
|
|
168
|
+
* };
|
|
169
|
+
* }
|
|
170
|
+
*
|
|
171
|
+
* // ❌ Wrong: called outside component context
|
|
172
|
+
* const signal = getSignal(); // Error: not in component
|
|
173
|
+
* ```
|
|
174
|
+
*/
|
|
175
|
+
declare function getSignal(): AbortSignal;
|
|
176
|
+
|
|
177
|
+
export { type ComponentFunction as C, type State as S, getSignal as g, state as s };
|
|
@@ -0,0 +1,373 @@
|
|
|
1
|
+
export { L as LayoutComponent, l as layout } from '../layout-BINPv-nz.js';
|
|
2
|
+
import '../types-uOPfcrdz.js';
|
|
3
|
+
import { J as JSXElement } from '../jsx-AzPM8gMS.js';
|
|
4
|
+
import { S as State } from '../component-BBGWJdqJ.js';
|
|
5
|
+
|
|
6
|
+
type SlotProps = {
|
|
7
|
+
asChild: true;
|
|
8
|
+
children: JSXElement;
|
|
9
|
+
[key: string]: unknown;
|
|
10
|
+
} | {
|
|
11
|
+
asChild?: false;
|
|
12
|
+
children?: unknown;
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* Slot
|
|
16
|
+
*
|
|
17
|
+
* Structural primitive for prop forwarding patterns.
|
|
18
|
+
*
|
|
19
|
+
* POLICY DECISIONS (LOCKED):
|
|
20
|
+
*
|
|
21
|
+
* 1. asChild Pattern
|
|
22
|
+
* When asChild=true, merges props into the single child element.
|
|
23
|
+
* Child must be a valid JSXElement; non-element children return null.
|
|
24
|
+
*
|
|
25
|
+
* 2. Fallback Behavior
|
|
26
|
+
* When asChild=false, returns a Fragment (structural no-op).
|
|
27
|
+
* No DOM element is introduced.
|
|
28
|
+
*
|
|
29
|
+
* 3. Type Safety
|
|
30
|
+
* asChild=true requires exactly one JSXElement child (enforced by type).
|
|
31
|
+
* Runtime validates with isElement() check.
|
|
32
|
+
*/
|
|
33
|
+
declare function Slot(props: SlotProps): JSXElement | null;
|
|
34
|
+
|
|
35
|
+
interface PresenceProps {
|
|
36
|
+
present: boolean | (() => boolean);
|
|
37
|
+
children?: unknown;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Presence
|
|
41
|
+
*
|
|
42
|
+
* Structural policy primitive for conditional mount/unmount.
|
|
43
|
+
* - No timers
|
|
44
|
+
* - No animation coupling
|
|
45
|
+
* - No DOM side-effects
|
|
46
|
+
*
|
|
47
|
+
* POLICY DECISIONS (LOCKED):
|
|
48
|
+
*
|
|
49
|
+
* 1. Present as Function
|
|
50
|
+
* Accepts boolean OR function to support lazy evaluation patterns.
|
|
51
|
+
* Function is called once per render. Use boolean form for static values.
|
|
52
|
+
*
|
|
53
|
+
* 2. Children Type
|
|
54
|
+
* `children` is intentionally `unknown` to remain runtime-agnostic.
|
|
55
|
+
* The runtime owns child normalization and validation.
|
|
56
|
+
*
|
|
57
|
+
* 3. Immediate Mount/Unmount
|
|
58
|
+
* No exit animations or transitions. When `present` becomes false,
|
|
59
|
+
* children are removed immediately. Animation must be layered above
|
|
60
|
+
* this primitive.
|
|
61
|
+
*/
|
|
62
|
+
declare function Presence({ present, children, }: PresenceProps): JSXElement | null;
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Portal / Host primitive.
|
|
66
|
+
*
|
|
67
|
+
* Foundations remain runtime-agnostic: a portal is an explicit read/write slot.
|
|
68
|
+
* Scheduling and attachment are owned by the runtime when `createPortalSlot`
|
|
69
|
+
* exists; otherwise this falls back to a local slot (deterministic, but does
|
|
70
|
+
* not schedule updates).
|
|
71
|
+
*
|
|
72
|
+
* POLICY DECISIONS (LOCKED):
|
|
73
|
+
*
|
|
74
|
+
* 1. Local Mutable State
|
|
75
|
+
* Foundations may use local mutable state ONLY to model deterministic slots,
|
|
76
|
+
* never to coordinate timing, effects, or ordering. The fallback mode uses
|
|
77
|
+
* closure-local `mounted` and `value` variables which are non-escaping and
|
|
78
|
+
* deterministic.
|
|
79
|
+
*
|
|
80
|
+
* 2. Return Type Philosophy
|
|
81
|
+
* Portal call signatures return `unknown` (intentionally opaque). The runtime
|
|
82
|
+
* owns the concrete type. This prevents foundations from assuming JSX.Element
|
|
83
|
+
* or DOM node types, maintaining runtime-agnostic portability.
|
|
84
|
+
*/
|
|
85
|
+
interface Portal<T = unknown> {
|
|
86
|
+
/** Mount point — rendered exactly once */
|
|
87
|
+
(): unknown;
|
|
88
|
+
/** Render content into the portal */
|
|
89
|
+
render(props: {
|
|
90
|
+
children?: T;
|
|
91
|
+
}): unknown;
|
|
92
|
+
}
|
|
93
|
+
declare function definePortal<T = unknown>(): Portal<T>;
|
|
94
|
+
declare const DefaultPortal: Portal<unknown>;
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* composeHandlers
|
|
98
|
+
*
|
|
99
|
+
* Compose two event handlers into one. The first handler runs, and unless it
|
|
100
|
+
* calls `event.preventDefault()` (or sets `defaultPrevented`), the second
|
|
101
|
+
* handler runs. This prevents accidental clobbering of child handlers when
|
|
102
|
+
* injecting props.
|
|
103
|
+
*
|
|
104
|
+
* POLICY DECISIONS (LOCKED):
|
|
105
|
+
*
|
|
106
|
+
* 1. Execution Order
|
|
107
|
+
* First handler runs before second (injected before base).
|
|
108
|
+
* This allows injected handlers to prevent default behavior.
|
|
109
|
+
*
|
|
110
|
+
* 2. Default Prevention Check
|
|
111
|
+
* By default, checks `defaultPrevented` on first argument.
|
|
112
|
+
* Can be disabled via options.checkDefaultPrevented = false.
|
|
113
|
+
*
|
|
114
|
+
* 3. Undefined Handler Support
|
|
115
|
+
* Undefined handlers are skipped (no-op). This simplifies usage
|
|
116
|
+
* where handlers are optional.
|
|
117
|
+
*
|
|
118
|
+
* 4. Type Safety
|
|
119
|
+
* Args are readonly to prevent mutation. Return type matches input.
|
|
120
|
+
*/
|
|
121
|
+
interface ComposeHandlersOptions {
|
|
122
|
+
/**
|
|
123
|
+
* When true (default), do not run the second handler if the first prevented default.
|
|
124
|
+
* When false, always run both handlers.
|
|
125
|
+
*/
|
|
126
|
+
checkDefaultPrevented?: boolean;
|
|
127
|
+
}
|
|
128
|
+
declare function composeHandlers<A extends readonly unknown[]>(first?: (...args: A) => void, second?: (...args: A) => void, options?: ComposeHandlersOptions): (...args: A) => void;
|
|
129
|
+
|
|
130
|
+
declare function mergeProps<TBase extends object, TInjected extends object>(base: TBase, injected: TInjected): TInjected & TBase;
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Tiny aria helpers
|
|
134
|
+
*/
|
|
135
|
+
declare function ariaDisabled(disabled?: boolean): {
|
|
136
|
+
'aria-disabled'?: 'true';
|
|
137
|
+
};
|
|
138
|
+
declare function ariaExpanded(expanded?: boolean): {
|
|
139
|
+
'aria-expanded'?: 'true' | 'false';
|
|
140
|
+
};
|
|
141
|
+
declare function ariaSelected(selected?: boolean): {
|
|
142
|
+
'aria-selected'?: 'true' | 'false';
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Ref composition utilities
|
|
147
|
+
*
|
|
148
|
+
* POLICY DECISIONS (LOCKED):
|
|
149
|
+
*
|
|
150
|
+
* 1. Ref Types Supported
|
|
151
|
+
* - Callback refs: (value: T | null) => void
|
|
152
|
+
* - Object refs: { current: T | null }
|
|
153
|
+
* - null/undefined (no-op)
|
|
154
|
+
*
|
|
155
|
+
* 2. Write Failure Handling
|
|
156
|
+
* setRef catches write failures (readonly refs) and ignores them.
|
|
157
|
+
* This is intentional — refs may be readonly in some contexts.
|
|
158
|
+
*
|
|
159
|
+
* 3. Composition Order
|
|
160
|
+
* composeRefs applies refs in array order (left to right).
|
|
161
|
+
* All refs are called even if one fails.
|
|
162
|
+
*/
|
|
163
|
+
type Ref<T> = ((value: T | null) => void) | {
|
|
164
|
+
current: T | null;
|
|
165
|
+
} | null | undefined;
|
|
166
|
+
declare function setRef<T>(ref: Ref<T>, value: T | null): void;
|
|
167
|
+
declare function composeRefs<T>(...refs: Array<Ref<T>>): (value: T | null) => void;
|
|
168
|
+
|
|
169
|
+
interface UseIdOptions {
|
|
170
|
+
/** Defaults to 'askr' */
|
|
171
|
+
prefix?: string;
|
|
172
|
+
/** Stable, caller-provided identity */
|
|
173
|
+
id: string | number;
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* useId
|
|
177
|
+
*
|
|
178
|
+
* Formats a stable ID from a caller-provided identity.
|
|
179
|
+
* - Pure and deterministic (no time/randomness/global counters)
|
|
180
|
+
* - SSR-safe
|
|
181
|
+
*
|
|
182
|
+
* POLICY DECISIONS (LOCKED):
|
|
183
|
+
*
|
|
184
|
+
* 1. No Auto-Generation
|
|
185
|
+
* Caller must provide the `id`. No random/sequential generation.
|
|
186
|
+
* This ensures determinism and SSR safety.
|
|
187
|
+
*
|
|
188
|
+
* 2. Format Convention
|
|
189
|
+
* IDs are formatted as `{prefix}-{id}`.
|
|
190
|
+
* Default prefix is "askr".
|
|
191
|
+
*
|
|
192
|
+
* 3. Type Coercion
|
|
193
|
+
* Numbers are coerced to strings via String().
|
|
194
|
+
* This is deterministic and consistent.
|
|
195
|
+
*/
|
|
196
|
+
declare function useId(options: UseIdOptions): string;
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* controllable
|
|
200
|
+
*
|
|
201
|
+
* Small utilities for controlled vs uncontrolled components. These helpers are
|
|
202
|
+
* intentionally minimal and do not manage state themselves; they help component
|
|
203
|
+
* implementations make correct decisions about when to call `onChange` vs
|
|
204
|
+
* update internal state.
|
|
205
|
+
*
|
|
206
|
+
* POLICY DECISIONS (LOCKED):
|
|
207
|
+
*
|
|
208
|
+
* 1. Controlled Detection
|
|
209
|
+
* A value is "controlled" if it is not `undefined`.
|
|
210
|
+
* This matches React conventions and is SSR-safe.
|
|
211
|
+
*
|
|
212
|
+
* 2. onChange Timing
|
|
213
|
+
* - Controlled mode: onChange called immediately, no internal update
|
|
214
|
+
* - Uncontrolled mode: internal state updated first, then onChange called
|
|
215
|
+
* This ensures onChange sees the new value in both modes.
|
|
216
|
+
*
|
|
217
|
+
* 3. Value Equality
|
|
218
|
+
* controllableState uses Object.is() to prevent unnecessary onChange calls.
|
|
219
|
+
* This is intentional — strict equality, no deep comparison.
|
|
220
|
+
*/
|
|
221
|
+
|
|
222
|
+
declare function isControlled<T>(value: T | undefined): value is T;
|
|
223
|
+
declare function resolveControllable<T>(value: T | undefined, defaultValue: T): {
|
|
224
|
+
value: T;
|
|
225
|
+
isControlled: boolean;
|
|
226
|
+
};
|
|
227
|
+
declare function makeControllable<T>(options: {
|
|
228
|
+
value: T | undefined;
|
|
229
|
+
defaultValue: T;
|
|
230
|
+
onChange?: (next: T) => void;
|
|
231
|
+
setInternal?: (next: T) => void;
|
|
232
|
+
}): {
|
|
233
|
+
set: (next: T) => void;
|
|
234
|
+
isControlled: boolean;
|
|
235
|
+
};
|
|
236
|
+
type ControllableState<T> = State<T> & {
|
|
237
|
+
isControlled: boolean;
|
|
238
|
+
};
|
|
239
|
+
/**
|
|
240
|
+
* controllableState
|
|
241
|
+
*
|
|
242
|
+
* Hook-like primitive that mirrors `state()` semantics while supporting
|
|
243
|
+
* controlled/uncontrolled behavior.
|
|
244
|
+
*/
|
|
245
|
+
declare function controllableState<T>(options: {
|
|
246
|
+
value: T | undefined;
|
|
247
|
+
defaultValue: T;
|
|
248
|
+
onChange?: (next: T) => void;
|
|
249
|
+
}): ControllableState<T>;
|
|
250
|
+
|
|
251
|
+
interface DefaultPreventable {
|
|
252
|
+
defaultPrevented?: boolean;
|
|
253
|
+
preventDefault?: () => void;
|
|
254
|
+
}
|
|
255
|
+
interface PropagationStoppable {
|
|
256
|
+
stopPropagation?: () => void;
|
|
257
|
+
}
|
|
258
|
+
interface KeyboardLikeEvent extends DefaultPreventable, PropagationStoppable {
|
|
259
|
+
key: string;
|
|
260
|
+
}
|
|
261
|
+
interface PointerLikeEvent extends DefaultPreventable, PropagationStoppable {
|
|
262
|
+
target?: unknown;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* pressable
|
|
267
|
+
*
|
|
268
|
+
* Interaction helper that produces VNode props for 'press' semantics.
|
|
269
|
+
* - Pure and deterministic: no DOM construction or mutation here
|
|
270
|
+
* - The runtime owns event attachment and scheduling
|
|
271
|
+
* - This helper returns plain props (handlers) intended to be attached by the runtime
|
|
272
|
+
*
|
|
273
|
+
* Behaviour:
|
|
274
|
+
* - For native buttons: only an `onClick` prop is provided (no ARIA or keyboard shims)
|
|
275
|
+
* - For non-button elements: add `role="button"` and `tabIndex` and keyboard handlers
|
|
276
|
+
* - Activation: `Enter` activates on keydown, `Space` activates on keyup (matches native button)
|
|
277
|
+
* - Disabled: handlers short-circuit and `aria-disabled` is set for all hosts
|
|
278
|
+
*
|
|
279
|
+
* POLICY DECISIONS (LOCKED):
|
|
280
|
+
*
|
|
281
|
+
* 1. Activation Timing (Platform Parity)
|
|
282
|
+
* - Enter fires on keydown (immediate response)
|
|
283
|
+
* - Space fires on keyup (allows cancel by moving focus, matches native)
|
|
284
|
+
* - Space keydown prevents scroll (matches native button behavior)
|
|
285
|
+
*
|
|
286
|
+
* 2. Disabled Enforcement Strategy
|
|
287
|
+
* - Native buttons: Use HTML `disabled` attribute (platform-enforced non-interactivity)
|
|
288
|
+
* AND `aria-disabled` (consistent a11y signaling)
|
|
289
|
+
* - Non-native: Use `tabIndex=-1` (removes from tab order)
|
|
290
|
+
* AND `aria-disabled` (signals disabled state to AT)
|
|
291
|
+
* - Click handler short-circuits as defense-in-depth (prevents leaked focus issues)
|
|
292
|
+
*
|
|
293
|
+
* 3. Key Repeat Behavior
|
|
294
|
+
* - Held Enter/Space will fire onPress repeatedly (matches native button)
|
|
295
|
+
* - No debouncing or repeat prevention (platform parity)
|
|
296
|
+
*/
|
|
297
|
+
interface PressableOptions {
|
|
298
|
+
disabled?: boolean;
|
|
299
|
+
onPress?: (e: PressEvent) => void;
|
|
300
|
+
/**
|
|
301
|
+
* Whether the host is a native button. Defaults to false.
|
|
302
|
+
*/
|
|
303
|
+
isNativeButton?: boolean;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
type PressEvent = DefaultPreventable & PropagationStoppable;
|
|
307
|
+
interface PressableResult {
|
|
308
|
+
onClick: (e: PressEvent) => void;
|
|
309
|
+
disabled?: true;
|
|
310
|
+
role?: 'button';
|
|
311
|
+
tabIndex?: number;
|
|
312
|
+
onKeyDown?: (e: KeyboardLikeEvent) => void;
|
|
313
|
+
onKeyUp?: (e: KeyboardLikeEvent) => void;
|
|
314
|
+
'aria-disabled'?: 'true';
|
|
315
|
+
}
|
|
316
|
+
declare function pressable({ disabled, onPress, isNativeButton, }: PressableOptions): PressableResult;
|
|
317
|
+
|
|
318
|
+
/**
|
|
319
|
+
* dismissable
|
|
320
|
+
*
|
|
321
|
+
* Provides props and helpers to support dismissal behaviour. This helper is
|
|
322
|
+
* runtime-agnostic:
|
|
323
|
+
* - It returns `onKeyDown` prop which will call onDismiss when Escape is
|
|
324
|
+
* pressed.
|
|
325
|
+
* - It also provides `outsideListener` factory which given an `isInside`
|
|
326
|
+
* predicate returns a handler suitable to attach at the document level that
|
|
327
|
+
* will call onDismiss when the pointerdown target is outside the component.
|
|
328
|
+
*/
|
|
329
|
+
interface DismissableOptions {
|
|
330
|
+
onDismiss?: () => void;
|
|
331
|
+
disabled?: boolean;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
declare function dismissable({ onDismiss, disabled }: DismissableOptions): {
|
|
335
|
+
onKeyDown: ((e: KeyboardLikeEvent) => void) | undefined;
|
|
336
|
+
outsideListener: ((isInside: (target: unknown) => boolean) => (e: PointerLikeEvent) => void) | undefined;
|
|
337
|
+
};
|
|
338
|
+
|
|
339
|
+
/**
|
|
340
|
+
* focusable
|
|
341
|
+
*
|
|
342
|
+
* Normalize focus-related props for hosts.
|
|
343
|
+
* - No DOM manipulation here; returns props that the runtime may attach.
|
|
344
|
+
*/
|
|
345
|
+
interface FocusableOptions {
|
|
346
|
+
disabled?: boolean;
|
|
347
|
+
tabIndex?: number | undefined;
|
|
348
|
+
}
|
|
349
|
+
interface FocusableResult {
|
|
350
|
+
tabIndex?: number;
|
|
351
|
+
'aria-disabled'?: 'true';
|
|
352
|
+
}
|
|
353
|
+
declare function focusable({ disabled, tabIndex, }: FocusableOptions): FocusableResult;
|
|
354
|
+
|
|
355
|
+
/**
|
|
356
|
+
* hoverable
|
|
357
|
+
*
|
|
358
|
+
* Produces props for pointer enter/leave handling. Pure and deterministic.
|
|
359
|
+
*/
|
|
360
|
+
interface HoverableOptions {
|
|
361
|
+
disabled?: boolean;
|
|
362
|
+
onEnter?: (e: HoverEvent) => void;
|
|
363
|
+
onLeave?: (e: HoverEvent) => void;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
type HoverEvent = DefaultPreventable & PropagationStoppable;
|
|
367
|
+
interface HoverableResult {
|
|
368
|
+
onPointerEnter?: (e: HoverEvent) => void;
|
|
369
|
+
onPointerLeave?: (e: HoverEvent) => void;
|
|
370
|
+
}
|
|
371
|
+
declare function hoverable({ disabled, onEnter, onLeave, }: HoverableOptions): HoverableResult;
|
|
372
|
+
|
|
373
|
+
export { type ComposeHandlersOptions, type ControllableState, DefaultPortal, type DismissableOptions, type FocusableOptions, type FocusableResult, type HoverableOptions, type HoverableResult, JSXElement, type Portal, Presence, type PresenceProps, type PressableOptions, type PressableResult, type Ref, Slot, type SlotProps, ariaDisabled, ariaExpanded, ariaSelected, composeHandlers, composeRefs, controllableState, definePortal, dismissable, focusable, hoverable, isControlled, makeControllable, mergeProps, pressable, resolveControllable, setRef, useId };
|