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,374 @@
1
+ import { bind } from "./cell.js";
2
+
3
+ // Reconcile arrays using Longest Increasing Subsequence (LIS) algorithm
4
+ // Heavily based on Ripple's reconciliation algorithms: https://github.com/Ripple-TS/ripple/blob/main/packages/ripple/src/runtime/internal/client/for.js
5
+
6
+ // LIS algorithm state (reused across calls for performance)
7
+ let lisResult;
8
+ let lisP;
9
+ let lisMaxLen = 0;
10
+
11
+ function lisAlgorithm(arr) {
12
+ let arrI = 0,
13
+ i = 0,
14
+ j = 0,
15
+ k = 0,
16
+ u = 0,
17
+ v = 0,
18
+ c = 0;
19
+ const len = arr.length;
20
+ if (len > lisMaxLen) {
21
+ lisMaxLen = len;
22
+ lisResult = new Int32Array(len);
23
+ lisP = new Int32Array(len);
24
+ }
25
+ while (i < len) {
26
+ arrI = arr[i];
27
+ if (arrI !== 0) {
28
+ j = lisResult[k];
29
+ if (arr[j] < arrI) {
30
+ lisP[i] = j;
31
+ lisResult[++k] = i;
32
+ i++;
33
+ continue;
34
+ }
35
+ u = 0;
36
+ v = k;
37
+ while (u < v) {
38
+ c = (u + v) >> 1;
39
+ if (arr[lisResult[c]] < arrI) {
40
+ u = c + 1;
41
+ } else {
42
+ v = c;
43
+ }
44
+ }
45
+ if (arrI < arr[lisResult[u]]) {
46
+ if (u > 0) lisP[i] = lisResult[u - 1];
47
+ lisResult[u] = i;
48
+ }
49
+ }
50
+ i++;
51
+ }
52
+ u = k + 1;
53
+ const seq = new Int32Array(u);
54
+ v = lisResult[u - 1];
55
+ while (u-- > 0) {
56
+ seq[u] = v;
57
+ v = lisP[v];
58
+ lisResult[u] = 0;
59
+ }
60
+ return seq;
61
+ }
62
+
63
+ /**
64
+ * Create an item block (lightweight object representing a rendered item)
65
+ * @param {Node} anchor - Where to insert
66
+ * @param {*} value - The data item
67
+ * @param {number} index - Array index
68
+ * @param {Function} renderFn - (anchor, value, index) => { start, end } or just appends nodes
69
+ */
70
+ function createItem(anchor, value, index, renderFn) {
71
+ // renderFn should return { start, end } nodes for the item
72
+ const item = renderFn(anchor, value, index);
73
+ return {
74
+ s: item, // state: { start, end } - the DOM range for this item
75
+ v: value,
76
+ };
77
+ }
78
+
79
+ /**
80
+ * Move a block's DOM nodes before an anchor
81
+ */
82
+ function moveItem(item, anchor) {
83
+ const state = item.s;
84
+ let node = state.start;
85
+ const end = state.end;
86
+
87
+ if (node !== end) {
88
+ while (node !== null) {
89
+ const next_node = node.nextSibling;
90
+ anchor.before(node);
91
+ if (next_node === end) {
92
+ anchor.before(end);
93
+ break;
94
+ }
95
+ node = next_node;
96
+ }
97
+ } else {
98
+ anchor.before(node);
99
+ }
100
+ }
101
+
102
+ /**
103
+ * Destroy an item's DOM nodes and run cleanup if present
104
+ */
105
+ function destroyItem(item) {
106
+ const state = item.s;
107
+ let node = state.start;
108
+ const end = state.end;
109
+
110
+ // Run cleanup function if the render provided one
111
+ if (state.cleanup) state.cleanup();
112
+
113
+ while (node !== null) {
114
+ const next = node.nextSibling;
115
+ node.remove();
116
+ if (node === end) break;
117
+ node = next;
118
+ }
119
+ }
120
+
121
+ /**
122
+ * Fast path: clear all items when going from non-empty to empty
123
+ */
124
+ function reconcileFastClear(anchor, forState, array) {
125
+ const parent_node = anchor.parentNode;
126
+ parent_node.textContent = "";
127
+ parent_node.append(anchor);
128
+ forState.array = array;
129
+ forState.items = [];
130
+ }
131
+
132
+ /**
133
+ * Reconcile arrays by reference equality
134
+ */
135
+ function reconcileByRef(anchor, forState, b, renderFn) {
136
+ let aStart = 0,
137
+ bStart = 0,
138
+ aLeft = 0,
139
+ bLeft = 0,
140
+ sources = new Int32Array(0),
141
+ moved = false,
142
+ pos = 0,
143
+ patched = 0,
144
+ i = 0,
145
+ j = 0;
146
+
147
+ const a = forState.array;
148
+ const aLen = a.length;
149
+ const bLen = b.length;
150
+
151
+ if (bLen !== 0) {
152
+ const bItems = Array(bLen);
153
+
154
+ // Empty -> non-empty: create all
155
+ if (aLen === 0) {
156
+ for (; j < bLen; j++) {
157
+ bItems[j] = createItem(anchor, b[j], j, renderFn);
158
+ }
159
+ forState.array = b;
160
+ forState.items = bItems;
161
+ return;
162
+ }
163
+
164
+ const aItems = forState.items;
165
+ let aVal = a[j];
166
+ let bVal = b[j];
167
+ let aEnd = aLen - 1;
168
+ let bEnd = bLen - 1;
169
+
170
+ // Skip common prefix
171
+ outer: {
172
+ while (aVal === bVal) {
173
+ a[j] = bVal;
174
+ bItems[j] = aItems[j];
175
+ if (++j > aEnd || j > bEnd) break outer;
176
+ aVal = a[j];
177
+ bVal = b[j];
178
+ }
179
+ // Skip common suffix
180
+ aVal = a[aEnd];
181
+ bVal = b[bEnd];
182
+ while (aVal === bVal) {
183
+ a[aEnd] = bVal;
184
+ bItems[bEnd] = aItems[aEnd];
185
+ bEnd--;
186
+ if (j > --aEnd || j > bEnd) break outer;
187
+ aVal = a[aEnd];
188
+ bVal = b[bEnd];
189
+ }
190
+ }
191
+
192
+ let fastPathRemoval = false;
193
+ let target;
194
+
195
+ if (j > aEnd) {
196
+ // Only additions
197
+ if (j <= bEnd) {
198
+ while (j <= bEnd) {
199
+ bVal = b[j];
200
+ target = j >= aLen ? anchor : aItems[j].s.start;
201
+ bItems[j] = createItem(target, bVal, j, renderFn);
202
+ j++;
203
+ }
204
+ }
205
+ } else if (j > bEnd) {
206
+ // Only removals
207
+ while (j <= aEnd) {
208
+ destroyItem(aItems[j++]);
209
+ }
210
+ } else {
211
+ // General case: need full reconciliation
212
+ aStart = j;
213
+ bStart = j;
214
+ aLeft = aEnd - j + 1;
215
+ bLeft = bEnd - j + 1;
216
+ sources = new Int32Array(bLeft);
217
+ moved = false;
218
+ pos = 0;
219
+ patched = 0;
220
+ i = 0;
221
+ fastPathRemoval = aLeft === aLen;
222
+
223
+ if (bLen < 4 || (aLeft | bLeft) < 32) {
224
+ // Small arrays: use O(n*m) search
225
+ for (i = aStart; i <= aEnd; ++i) {
226
+ aVal = a[i];
227
+ if (patched < bLeft) {
228
+ for (j = bStart; j <= bEnd; j++) {
229
+ if (aVal === (bVal = b[j])) {
230
+ sources[j - bStart] = i + 1;
231
+ if (fastPathRemoval) {
232
+ fastPathRemoval = false;
233
+ while (aStart < i) destroyItem(aItems[aStart++]);
234
+ }
235
+ if (pos > j) moved = true;
236
+ else pos = j;
237
+ bItems[j] = aItems[i];
238
+ ++patched;
239
+ break;
240
+ }
241
+ }
242
+ if (!fastPathRemoval && j > bEnd) destroyItem(aItems[i]);
243
+ } else if (!fastPathRemoval) {
244
+ destroyItem(aItems[i]);
245
+ }
246
+ }
247
+ } else {
248
+ // Larger arrays: use Map for O(n+m) lookup
249
+ const map = new Map();
250
+ for (i = bStart; i <= bEnd; ++i) map.set(b[i], i);
251
+ for (i = aStart; i <= aEnd; ++i) {
252
+ aVal = a[i];
253
+ if (patched < bLeft) {
254
+ j = map.get(aVal);
255
+ if (j !== undefined) {
256
+ if (fastPathRemoval) {
257
+ fastPathRemoval = false;
258
+ while (i > aStart) destroyItem(aItems[aStart++]);
259
+ }
260
+ sources[j - bStart] = i + 1;
261
+ if (pos > j) moved = true;
262
+ else pos = j;
263
+ bItems[j] = aItems[i];
264
+ ++patched;
265
+ } else if (!fastPathRemoval) {
266
+ destroyItem(aItems[i]);
267
+ }
268
+ } else if (!fastPathRemoval) {
269
+ destroyItem(aItems[i]);
270
+ }
271
+ }
272
+ }
273
+
274
+ if (fastPathRemoval) {
275
+ reconcileFastClear(anchor, forState, []);
276
+ reconcileByRef(anchor, forState, b, renderFn);
277
+ return;
278
+ }
279
+
280
+ if (moved) {
281
+ let nextPos = 0;
282
+ const seq = lisAlgorithm(sources);
283
+ j = seq.length - 1;
284
+ for (i = bLeft - 1; i >= 0; i--) {
285
+ pos = i + bStart;
286
+ nextPos = pos + 1;
287
+ target = nextPos < bLen ? bItems[nextPos].s.start : anchor;
288
+
289
+ if (sources[i] === 0) {
290
+ bVal = b[pos];
291
+ bItems[pos] = createItem(target, bVal, pos, renderFn);
292
+ } else if (j < 0 || i !== seq[j]) {
293
+ moveItem(bItems[pos], target);
294
+ } else {
295
+ j--;
296
+ }
297
+ }
298
+ } else if (patched !== bLeft) {
299
+ for (i = bLeft - 1; i >= 0; i--) {
300
+ if (sources[i] === 0) {
301
+ pos = i + bStart;
302
+ bVal = b[pos];
303
+ const nextPos = pos + 1;
304
+ target = nextPos < bLen ? bItems[nextPos].s.start : anchor;
305
+ bItems[pos] = createItem(target, bVal, pos, renderFn);
306
+ }
307
+ }
308
+ }
309
+ }
310
+
311
+ forState.array = b;
312
+ forState.items = bItems;
313
+ } else if (aLen > 0) {
314
+ // Non-empty -> empty: clear all
315
+ reconcileFastClear(anchor, forState, b);
316
+ }
317
+ }
318
+
319
+ /**
320
+ * Create a forBlock for efficient list rendering
321
+ * @param {Element} container - The container element (e.g., tbody)
322
+ * @param {Object} sourceCell - A cell containing the array to render
323
+ * @param {Function} renderFn - (anchor, item, index) => { start, end, cleanup? }
324
+ * @returns {{ update: Function, state: Object, destroy: Function }}
325
+ */
326
+ export function forBlock(container, sourceCell, renderFn) {
327
+ // Create anchor node at end of container
328
+ const anchor = document.createTextNode("");
329
+ container.appendChild(anchor);
330
+
331
+ // Initialize state
332
+ const forState = {
333
+ array: [],
334
+ items: [],
335
+ };
336
+
337
+ const doUpdate = () => {
338
+ const collection = sourceCell.v;
339
+ const array = Array.isArray(collection)
340
+ ? collection
341
+ : collection == null
342
+ ? []
343
+ : Array.from(collection);
344
+ reconcileByRef(anchor, forState, array, renderFn);
345
+ };
346
+
347
+ // Subscribe to cell changes
348
+ const unsubscribe = bind(sourceCell, doUpdate);
349
+
350
+ // Initial render
351
+ doUpdate();
352
+
353
+ // Destroy function for cleanup
354
+ const destroy = () => {
355
+ unsubscribe();
356
+ // Destroy all current items
357
+ const items = forState.items;
358
+ for (let i = 0; i < items.length; i++) {
359
+ destroyItem(items[i]);
360
+ }
361
+ forState.array = [];
362
+ forState.items = [];
363
+ anchor.remove();
364
+ };
365
+
366
+ // Return controller object
367
+ return {
368
+ update: doUpdate,
369
+ destroy,
370
+ get state() {
371
+ return forState;
372
+ },
373
+ };
374
+ }
@@ -0,0 +1,17 @@
1
+ // Template
2
+ export { template, svgTemplate } from "./template.js";
3
+
4
+ // Reactive primitives
5
+ export { cell, get, put, bind, notify, set } from "./cell.js";
6
+
7
+ // Event delegation
8
+ export { delegate, handleRootEvents } from "./events.js";
9
+
10
+ // Component definition
11
+ export { defineComponent, setProp, getProps } from "./component.js";
12
+
13
+ // List rendering
14
+ export { forBlock } from "./for-block.js";
15
+
16
+ // Conditional rendering
17
+ export { showBlock } from "./show-block.js";
@@ -0,0 +1,115 @@
1
+ import { bind } from "./cell.js";
2
+
3
+ /**
4
+ * Create a showBlock for conditional rendering
5
+ * @param {Element} container - The container element
6
+ * @param {Object|Function} condition - A cell containing the condition, or a getter function returning boolean
7
+ * @param {Function} renderFn - (anchor) => { start, end, cleanup? }
8
+ * @param {Array} [deps] - Optional array of cells to subscribe to for reactive updates (for complex expressions)
9
+ * @returns {{ update: Function, destroy: Function }}
10
+ */
11
+ export function showBlock(container, condition, renderFn, deps) {
12
+ // Create anchor node at end of container
13
+ const anchor = document.createTextNode("");
14
+ container.appendChild(anchor);
15
+
16
+ // Track current rendered state: { start, end, cleanup? } or null
17
+ let currentState = null;
18
+
19
+ // Determine if condition is a cell, a getter function, or a static value
20
+ const isCell = condition && typeof condition === "object" && "v" in condition;
21
+ const isGetter = typeof condition === "function";
22
+
23
+ const create = () => {
24
+ if (currentState) return; // Already showing
25
+ currentState = renderFn(anchor);
26
+ };
27
+
28
+ const destroyCurrent = () => {
29
+ if (!currentState) return; // Nothing to destroy
30
+
31
+ // Run cleanup function if provided
32
+ if (currentState.cleanup) {
33
+ currentState.cleanup();
34
+ }
35
+
36
+ // Remove DOM nodes
37
+ let node = currentState.start;
38
+ const end = currentState.end;
39
+ do {
40
+ const next = node.nextSibling;
41
+ node.remove();
42
+ if (node === end) break;
43
+ node = next;
44
+ } while (node);
45
+
46
+ currentState = null;
47
+ };
48
+
49
+ // Optimized update functions based on condition type
50
+ const doUpdate = isCell
51
+ ? () => {
52
+ if (condition.v) {
53
+ if (!currentState) create();
54
+ } else if (currentState) {
55
+ destroyCurrent();
56
+ }
57
+ }
58
+ : isGetter
59
+ ? () => {
60
+ if (condition()) {
61
+ if (!currentState) create();
62
+ } else if (currentState) {
63
+ destroyCurrent();
64
+ }
65
+ }
66
+ : () => {
67
+ // Static value - only runs once
68
+ if (condition && !currentState) create();
69
+ };
70
+
71
+ // Subscribe to cell changes
72
+ // Optimize for common case: single subscription doesn't need array
73
+ let unsubscribe = null;
74
+ let unsubscribes = null;
75
+
76
+ const depsLen = deps ? deps.length : 0;
77
+
78
+ if (isCell) {
79
+ // Simple cell condition
80
+ unsubscribe = bind(condition, doUpdate);
81
+ } else if (depsLen === 1) {
82
+ // Single dependency - no array needed
83
+ unsubscribe = bind(deps[0], doUpdate);
84
+ } else if (depsLen > 1) {
85
+ // Multiple dependencies - use array
86
+ unsubscribes = [];
87
+ for (let i = 0; i < depsLen; i++) {
88
+ unsubscribes.push(bind(deps[i], doUpdate));
89
+ }
90
+ }
91
+
92
+ // Initial render
93
+ doUpdate();
94
+
95
+ // Destroy function for cleanup
96
+ const destroy = () => {
97
+ if (unsubscribe) {
98
+ unsubscribe();
99
+ } else if (unsubscribes) {
100
+ for (let i = 0; i < unsubscribes.length; i++) {
101
+ unsubscribes[i]();
102
+ }
103
+ }
104
+ destroyCurrent();
105
+ anchor.remove();
106
+ };
107
+
108
+ return {
109
+ update: doUpdate,
110
+ destroy,
111
+ get isShowing() {
112
+ return currentState !== null;
113
+ },
114
+ };
115
+ }
@@ -0,0 +1,32 @@
1
+ const { cloneNode } = Node.prototype;
2
+
3
+ /**
4
+ * Create a template from an HTML string and return a clone function
5
+ * @param {string} html - The HTML string to create a template from
6
+ * @returns {() => Node} - A function that returns a deep clone of the template content
7
+ */
8
+ export const template = (html) => {
9
+ const t = document.createElement("template");
10
+ t.innerHTML = html;
11
+ return () => cloneNode.call(t.content, true);
12
+ };
13
+
14
+ /**
15
+ * Create a template from an SVG string and return a clone function.
16
+ * SVG elements must be created in the SVG namespace to render correctly.
17
+ * @param {string} svg - The SVG string (can be a full <svg> or inner SVG content)
18
+ * @returns {() => Node} - A function that returns a deep clone of the SVG content
19
+ */
20
+ export const svgTemplate = (svg) => {
21
+ // Wrap in an SVG element to ensure proper namespace parsing
22
+ const wrapper = document.createElementNS("http://www.w3.org/2000/svg", "svg");
23
+ wrapper.innerHTML = svg;
24
+
25
+ // Use a document fragment to match the behavior of the regular template function
26
+ // The traversal code expects to call .firstChild on the result
27
+ const fragment = document.createDocumentFragment();
28
+ while (wrapper.firstChild) {
29
+ fragment.appendChild(wrapper.firstChild);
30
+ }
31
+ return () => cloneNode.call(fragment, true);
32
+ };
@@ -0,0 +1,9 @@
1
+ export function compile(code: string, filename: string): { code: string; map: any };
2
+ export function parse(code: string, filename: string): any;
3
+ export function validateNoCustomComponents(ast: any): void;
4
+ export function generateOutput(
5
+ code: string,
6
+ ast: any,
7
+ filename: string,
8
+ ): { code: string; map: any };
9
+ export function inlineGetCalls(code: string, filename: string): { code: string; map: any };