@askrjs/askr 0.0.6 → 0.0.8

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