roqa 0.0.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.
@@ -0,0 +1,433 @@
1
+ // ============================================
2
+ // Reactive Primitives
3
+ // ============================================
4
+
5
+ /**
6
+ * A reactive cell - a container for a value that can be observed
7
+ */
8
+ export interface Cell<T> {
9
+ /** Current value */
10
+ v: T;
11
+ /** Effect subscribers */
12
+ e: Array<(value: T) => void>;
13
+ }
14
+
15
+ /**
16
+ * Create a reactive cell with a static value
17
+ */
18
+ export function cell<T>(initialValue: T): Cell<T>;
19
+
20
+ /**
21
+ * Helper type to unwrap the return type of a function stored in a cell.
22
+ * If T is a function, returns its return type. Otherwise returns T.
23
+ */
24
+ type Unwrap<T> = T extends () => infer R ? R : T;
25
+
26
+ /**
27
+ * Get the current value of a cell.
28
+ * If the cell contains a function (derived cell), the function is executed
29
+ * and its return value is returned.
30
+ */
31
+ export function get<T>(cell: Cell<T>): Unwrap<T>;
32
+
33
+ /**
34
+ * Set a cell's value without triggering effects
35
+ */
36
+ export function put<T>(cell: Cell<T>, value: T): void;
37
+
38
+ /**
39
+ * Set a cell's value and trigger effects
40
+ */
41
+ export function set<T>(cell: Cell<T>, value: T): void;
42
+
43
+ /**
44
+ * Bind an effect function to a cell
45
+ * @returns Unsubscribe function
46
+ */
47
+ export function bind<T>(cell: Cell<T>, effect: (value: T) => void): () => void;
48
+
49
+ /**
50
+ * Notify all effects bound to a cell
51
+ */
52
+ export function notify<T>(cell: Cell<T>): void;
53
+
54
+ // ============================================
55
+ // Template
56
+ // ============================================
57
+
58
+ /**
59
+ * Create a template from an HTML string
60
+ * @returns A function that clones the template
61
+ */
62
+ export function template(html: string): () => Node;
63
+
64
+ // ============================================
65
+ // Event Delegation
66
+ // ============================================
67
+
68
+ /**
69
+ * Register event types for delegation
70
+ */
71
+ export function delegate(events: string[]): void;
72
+
73
+ /**
74
+ * Set up event handling on a root element
75
+ * @returns Cleanup function
76
+ */
77
+ export function handleRootEvents(target: EventTarget): () => void;
78
+
79
+ // ============================================
80
+ // Component Definition
81
+ // ============================================
82
+
83
+ /**
84
+ * Props object passed to component functions
85
+ */
86
+ export type ComponentProps = Record<string, unknown>;
87
+
88
+ /**
89
+ * Options for emitting custom events
90
+ */
91
+ export interface EmitOptions {
92
+ /** Whether the event bubbles up through the DOM. Default: true */
93
+ bubbles?: boolean;
94
+ /** Whether the event can cross shadow DOM boundaries. Default: false */
95
+ composed?: boolean;
96
+ }
97
+
98
+ /**
99
+ * The custom element instance passed as `this` to component functions.
100
+ * Use this type to annotate the `this` parameter in your component functions.
101
+ *
102
+ * The generic parameter `T` allows you to specify additional properties that
103
+ * will be attached to the element at runtime (e.g., methods for other components to call).
104
+ *
105
+ * @example
106
+ * ```tsx
107
+ * import { defineComponent, type RoqaElement } from "roqa";
108
+ *
109
+ * // Basic usage
110
+ * function MyComponent(this: RoqaElement) {
111
+ * this.connected(() => console.log("Connected!"));
112
+ * return <div>Hello</div>;
113
+ * }
114
+ *
115
+ * // With custom methods attached at runtime
116
+ * interface CounterMethods {
117
+ * setCount: (value: number) => void;
118
+ * reset: () => void;
119
+ * }
120
+ *
121
+ * function Counter(this: RoqaElement<CounterMethods>) {
122
+ * const count = cell(0);
123
+ * this.setCount = (value) => set(count, value);
124
+ * this.reset = () => set(count, 0);
125
+ * return <p>Count: {get(count)}</p>;
126
+ * }
127
+ *
128
+ * defineComponent("my-component", MyComponent);
129
+ * defineComponent("x-counter", Counter);
130
+ * ```
131
+ */
132
+ export type RoqaElement<T extends object = {}> = HTMLElement & RoqaElementMethods & T;
133
+
134
+ /**
135
+ * A form-associated Roqa element with access to ElementInternals.
136
+ * Use this type when your component has `formAssociated: true`.
137
+ *
138
+ * @example
139
+ * ```tsx
140
+ * function MyInput(this: FormAssociatedRoqaElement<MyInputMethods>, props: MyInputProps) {
141
+ * this.connected(() => {
142
+ * // Access form internals
143
+ * this.internals.setFormValue(value);
144
+ * this.internals.setValidity({ valueMissing: true }, "Required");
145
+ * });
146
+ * }
147
+ *
148
+ * defineComponent("my-input", MyInput, { formAssociated: true });
149
+ * ```
150
+ */
151
+ export type FormAssociatedRoqaElement<T extends object = {}> = RoqaElement<T> & {
152
+ /**
153
+ * The ElementInternals object for form association.
154
+ * Provides access to form submission, validation, and accessibility features.
155
+ */
156
+ internals: ElementInternals;
157
+ };
158
+
159
+ /**
160
+ * Methods available on all Roqa custom elements
161
+ */
162
+ export interface RoqaElementMethods {
163
+ /**
164
+ * Register a callback to run when the component is connected to the DOM.
165
+ * Multiple callbacks can be registered and will run in order.
166
+ *
167
+ * If the callback returns a function, it will be called when the component
168
+ * disconnects (useful for cleanup like unsubscribing from cells).
169
+ *
170
+ * @example
171
+ * ```tsx
172
+ * this.connected(() => {
173
+ * const unbind = bind(someCell, (value) => {
174
+ * // react to changes
175
+ * });
176
+ * // Return cleanup function
177
+ * return unbind;
178
+ * });
179
+ * ```
180
+ */
181
+ connected(callback: () => void | (() => void)): void;
182
+
183
+ /**
184
+ * Register a callback to run when the component is disconnected from the DOM.
185
+ * Use this for cleanup (removing event listeners, canceling timers, etc.)
186
+ */
187
+ disconnected(callback: () => void): void;
188
+
189
+ /**
190
+ * Add an event listener that is automatically removed when the component disconnects.
191
+ * This is a convenience wrapper around addEventListener with AbortController.
192
+ *
193
+ * Supports both native DOM events and custom events:
194
+ * ```tsx
195
+ * // Native DOM events are automatically typed
196
+ * this.on("click", (event) => {
197
+ * console.log(event.clientX); // MouseEvent
198
+ * });
199
+ *
200
+ * this.on("keydown", (event) => {
201
+ * console.log(event.key); // KeyboardEvent
202
+ * });
203
+ *
204
+ * // Custom events with typed detail
205
+ * this.on<{ count: number }>("count-changed", (event) => {
206
+ * console.log(event.detail.count); // CustomEvent<{ count: number }>
207
+ * });
208
+ * ```
209
+ */
210
+ on<K extends keyof HTMLElementEventMap>(
211
+ eventName: K,
212
+ handler: (event: HTMLElementEventMap[K]) => void,
213
+ ): void;
214
+ on<T = unknown>(eventName: string, handler: (event: CustomEvent<T>) => void): void;
215
+
216
+ /**
217
+ * Emit a custom event from this component.
218
+ * @param eventName - The name of the custom event
219
+ * @param detail - Optional data to include with the event
220
+ * @param options - Optional event options (bubbles, composed)
221
+ */
222
+ emit<T = unknown>(eventName: string, detail?: T, options?: EmitOptions): void;
223
+
224
+ /**
225
+ * Toggle a boolean attribute based on a condition.
226
+ * When true, the attribute is present (empty string value).
227
+ * When false, the attribute is removed.
228
+ *
229
+ * @param name - The attribute name
230
+ * @param condition - Whether the attribute should be present
231
+ *
232
+ * @example
233
+ * ```tsx
234
+ * this.toggleAttr("disabled", isDisabled);
235
+ * // true → <my-element disabled>
236
+ * // false → <my-element>
237
+ * ```
238
+ */
239
+ toggleAttr(name: string, condition: boolean): void;
240
+
241
+ /**
242
+ * Set a state attribute with mutually exclusive on/off variants.
243
+ * Creates a pair of attributes where only one is present at a time.
244
+ * The "off" variant is prefixed with "un" (e.g., "checked" / "unchecked").
245
+ *
246
+ * @param name - The base attribute name
247
+ * @param condition - The state value
248
+ *
249
+ * @example
250
+ * ```tsx
251
+ * this.stateAttr("checked", isChecked);
252
+ * // true → <my-element checked>
253
+ * // false → <my-element unchecked>
254
+ * ```
255
+ */
256
+ stateAttr(name: string, condition: boolean): void;
257
+
258
+ /**
259
+ * Register a callback for when an observed attribute changes.
260
+ * The attribute must be declared in the `observedAttributes` option of defineComponent.
261
+ * Uses the native `attributeChangedCallback` lifecycle method.
262
+ *
263
+ * @param name - The attribute name (must be in observedAttributes)
264
+ * @param callback - Called when attribute changes
265
+ *
266
+ * @example
267
+ * ```tsx
268
+ * // Declare observed attributes in defineComponent:
269
+ * defineComponent("my-switch", MySwitch, {
270
+ * observedAttributes: ["checked", "disabled"]
271
+ * });
272
+ *
273
+ * // Handle changes in component:
274
+ * this.attrChanged("checked", (newValue, oldValue) => {
275
+ * console.log("checked changed from", oldValue, "to", newValue);
276
+ * });
277
+ * ```
278
+ */
279
+ attrChanged(
280
+ name: string,
281
+ callback: (newValue: string | null, oldValue: string | null) => void,
282
+ ): void;
283
+ }
284
+
285
+ /**
286
+ * Component function type.
287
+ * @template P - Props type passed to the component
288
+ * @template E - Runtime methods/properties attached to the element
289
+ */
290
+ export type ComponentFunction<
291
+ P extends ComponentProps = ComponentProps,
292
+ E extends object = object,
293
+ > = (this: RoqaElement<E>, props: P) => void;
294
+
295
+ /**
296
+ * Set a prop on an element (works before element is upgraded)
297
+ * Props are stored in a WeakMap and retrieved when the element connects
298
+ */
299
+ export function setProp(element: Element, propName: string, value: unknown): void;
300
+
301
+ /**
302
+ * Get all props for an element
303
+ */
304
+ export function getProps(element: Element): ComponentProps;
305
+
306
+ /**
307
+ * Options for defineComponent
308
+ */
309
+ export interface DefineComponentOptions {
310
+ /**
311
+ * Attributes to observe for changes via attributeChangedCallback.
312
+ * Use `this.attrChanged()` in your component to handle changes.
313
+ */
314
+ observedAttributes?: string[];
315
+ /**
316
+ * Whether the element participates in form submission.
317
+ * When true, the element gets access to `this.internals` (ElementInternals)
318
+ * for form value submission, validation, and accessibility.
319
+ *
320
+ * @example
321
+ * ```tsx
322
+ * defineComponent("my-switch", MySwitch, {
323
+ * formAssociated: true,
324
+ * observedAttributes: ["checked"]
325
+ * });
326
+ * ```
327
+ */
328
+ formAssociated?: boolean;
329
+ }
330
+
331
+ /**
332
+ * Define a custom element component
333
+ * @param tagName - The custom element tag name (must contain a hyphen)
334
+ * @param fn - The component function that receives props as its first argument
335
+ * @param options - Optional configuration (observedAttributes, etc.)
336
+ */
337
+ export function defineComponent<
338
+ P extends ComponentProps = ComponentProps,
339
+ E extends object = object,
340
+ >(tagName: string, fn: ComponentFunction<P, E>, options?: DefineComponentOptions): void;
341
+
342
+ // ============================================
343
+ // List Rendering
344
+ // ============================================
345
+
346
+ export interface ForBlockState<T> {
347
+ array: T[];
348
+ items: Array<{ s: { start: Node; end: Node; cleanup?: () => void }; v: T }>;
349
+ }
350
+
351
+ export interface ForBlockController<T> {
352
+ update: () => void;
353
+ destroy: () => void;
354
+ readonly state: ForBlockState<T>;
355
+ }
356
+
357
+ /**
358
+ * Create a reactive for loop block
359
+ */
360
+ export function forBlock<T>(
361
+ container: Element,
362
+ source: Cell<T[]>,
363
+ render: (
364
+ anchor: Node,
365
+ item: T,
366
+ index: number,
367
+ ) => { start: Node; end: Node; cleanup?: () => void },
368
+ ): ForBlockController<T>;
369
+
370
+ // ============================================
371
+ // JSX Control Flow Components
372
+ // ============================================
373
+
374
+ /**
375
+ * Props for the For component
376
+ */
377
+ export interface ForProps<T> {
378
+ /** Reactive cell containing the array to iterate */
379
+ each: Cell<T[]>;
380
+ /** Render callback for each item */
381
+ children: (item: T, index: number) => void;
382
+ }
383
+
384
+ /**
385
+ * For component - renders a list reactively.
386
+ * This is a compile-time component that gets transformed into forBlock() calls.
387
+ *
388
+ * @example
389
+ * ```tsx
390
+ * import { cell, For } from "roqa";
391
+ *
392
+ * const items = cell([
393
+ * { label: "A", value: 30 },
394
+ * { label: "B", value: 80 },
395
+ * ]);
396
+ *
397
+ * <For each={items}>
398
+ * {(item, index) => (
399
+ * <div>
400
+ * {index}: {item.label} = {item.value}
401
+ * </div>
402
+ * )}
403
+ * </For>
404
+ * ```
405
+ */
406
+ export declare function For<T>(props: ForProps<T>): JSX.Element;
407
+
408
+ /**
409
+ * Props for the Show component
410
+ */
411
+ export interface ShowProps {
412
+ /** Condition to evaluate - content is shown when truthy */
413
+ when: boolean;
414
+ /** Content to render when condition is true */
415
+ children: unknown;
416
+ }
417
+
418
+ /**
419
+ * Show component - conditionally renders content.
420
+ * This is a compile-time component that gets transformed into showBlock() calls.
421
+ *
422
+ * @example
423
+ * ```tsx
424
+ * import { cell, get, Show } from "roqa";
425
+ *
426
+ * const isVisible = cell(true);
427
+ *
428
+ * <Show when={get(isVisible)}>
429
+ * <div>This content is conditionally shown</div>
430
+ * </Show>
431
+ * ```
432
+ */
433
+ export declare function Show(props: ShowProps): JSX.Element;