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.
- package/CHANGELOG.md +21 -0
- package/LICENSE +21 -0
- package/README.md +33 -0
- package/package.json +77 -0
- package/src/compiler/codegen.js +1217 -0
- package/src/compiler/index.js +47 -0
- package/src/compiler/parser.js +197 -0
- package/src/compiler/transforms/bind-detector.js +264 -0
- package/src/compiler/transforms/events.js +246 -0
- package/src/compiler/transforms/for-transform.js +164 -0
- package/src/compiler/transforms/inline-get.js +1049 -0
- package/src/compiler/transforms/jsx-to-template.js +871 -0
- package/src/compiler/transforms/show-transform.js +78 -0
- package/src/compiler/transforms/validate.js +80 -0
- package/src/compiler/utils.js +69 -0
- package/src/jsx-runtime.d.ts +640 -0
- package/src/jsx-runtime.js +73 -0
- package/src/runtime/cell.js +37 -0
- package/src/runtime/component.js +241 -0
- package/src/runtime/events.js +156 -0
- package/src/runtime/for-block.js +374 -0
- package/src/runtime/index.js +17 -0
- package/src/runtime/show-block.js +115 -0
- package/src/runtime/template.js +32 -0
- package/types/compiler.d.ts +9 -0
- package/types/index.d.ts +433 -0
package/types/index.d.ts
ADDED
|
@@ -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;
|