gonia 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.
Files changed (54) hide show
  1. package/README.md +119 -0
  2. package/dist/client/hydrate.d.ts +54 -0
  3. package/dist/client/hydrate.js +445 -0
  4. package/dist/client/index.d.ts +7 -0
  5. package/dist/client/index.js +6 -0
  6. package/dist/context.d.ts +40 -0
  7. package/dist/context.js +69 -0
  8. package/dist/directives/class.d.ts +21 -0
  9. package/dist/directives/class.js +42 -0
  10. package/dist/directives/for.d.ts +29 -0
  11. package/dist/directives/for.js +265 -0
  12. package/dist/directives/html.d.ts +16 -0
  13. package/dist/directives/html.js +19 -0
  14. package/dist/directives/if.d.ts +25 -0
  15. package/dist/directives/if.js +133 -0
  16. package/dist/directives/index.d.ts +15 -0
  17. package/dist/directives/index.js +15 -0
  18. package/dist/directives/model.d.ts +27 -0
  19. package/dist/directives/model.js +134 -0
  20. package/dist/directives/on.d.ts +21 -0
  21. package/dist/directives/on.js +54 -0
  22. package/dist/directives/show.d.ts +15 -0
  23. package/dist/directives/show.js +19 -0
  24. package/dist/directives/slot.d.ts +48 -0
  25. package/dist/directives/slot.js +99 -0
  26. package/dist/directives/template.d.ts +55 -0
  27. package/dist/directives/template.js +147 -0
  28. package/dist/directives/text.d.ts +15 -0
  29. package/dist/directives/text.js +18 -0
  30. package/dist/expression.d.ts +60 -0
  31. package/dist/expression.js +96 -0
  32. package/dist/index.d.ts +19 -0
  33. package/dist/index.js +16 -0
  34. package/dist/inject.d.ts +42 -0
  35. package/dist/inject.js +63 -0
  36. package/dist/providers.d.ts +96 -0
  37. package/dist/providers.js +146 -0
  38. package/dist/reactivity.d.ts +95 -0
  39. package/dist/reactivity.js +219 -0
  40. package/dist/scope.d.ts +43 -0
  41. package/dist/scope.js +112 -0
  42. package/dist/server/index.d.ts +7 -0
  43. package/dist/server/index.js +6 -0
  44. package/dist/server/render.d.ts +61 -0
  45. package/dist/server/render.js +243 -0
  46. package/dist/templates.d.ts +92 -0
  47. package/dist/templates.js +124 -0
  48. package/dist/types.d.ts +362 -0
  49. package/dist/types.js +110 -0
  50. package/dist/vite/index.d.ts +6 -0
  51. package/dist/vite/index.js +6 -0
  52. package/dist/vite/plugin.d.ts +30 -0
  53. package/dist/vite/plugin.js +127 -0
  54. package/package.json +67 -0
@@ -0,0 +1,219 @@
1
+ /**
2
+ * Fine-grained reactivity system using Proxies.
3
+ *
4
+ * @remarks
5
+ * Each directive becomes its own effect, tracking only the state it accesses.
6
+ * Changes trigger only the affected effects - no component re-renders, no diffing.
7
+ *
8
+ * @packageDocumentation
9
+ */
10
+ let activeEffect = null;
11
+ let activeScope = null;
12
+ const targetMap = new WeakMap();
13
+ const effectDeps = new Map();
14
+ export function createEffectScope() {
15
+ const scope = {
16
+ _effects: [],
17
+ active: true,
18
+ run(fn) {
19
+ const prevScope = activeScope;
20
+ activeScope = scope;
21
+ try {
22
+ return fn();
23
+ }
24
+ finally {
25
+ activeScope = prevScope;
26
+ }
27
+ },
28
+ stop() {
29
+ if (scope.active) {
30
+ for (const stopFn of scope._effects) {
31
+ stopFn();
32
+ }
33
+ scope._effects.length = 0;
34
+ scope.active = false;
35
+ }
36
+ }
37
+ };
38
+ return scope;
39
+ }
40
+ /**
41
+ * Register an effect's stop function with the active scope.
42
+ *
43
+ * @internal
44
+ */
45
+ function registerWithScope(stopFn) {
46
+ if (activeScope && activeScope.active) {
47
+ activeScope._effects.push(stopFn);
48
+ }
49
+ }
50
+ /**
51
+ * Make an object deeply reactive.
52
+ *
53
+ * @remarks
54
+ * Property access is tracked when inside an effect. Mutations trigger
55
+ * all effects that depend on the changed property.
56
+ *
57
+ * @typeParam T - Object type
58
+ * @param target - The object to make reactive
59
+ * @returns A reactive proxy of the object
60
+ *
61
+ * @example
62
+ * ```ts
63
+ * const state = reactive({ count: 0 });
64
+ * effect(() => console.log(state.count));
65
+ * state.count = 1; // logs: 1
66
+ * ```
67
+ */
68
+ export function reactive(target) {
69
+ return new Proxy(target, {
70
+ get(obj, key, receiver) {
71
+ track(obj, key);
72
+ const value = Reflect.get(obj, key, receiver);
73
+ if (value !== null && typeof value === 'object') {
74
+ return reactive(value);
75
+ }
76
+ return value;
77
+ },
78
+ set(obj, key, value, receiver) {
79
+ const oldValue = Reflect.get(obj, key, receiver);
80
+ const result = Reflect.set(obj, key, value, receiver);
81
+ if (oldValue !== value) {
82
+ trigger(obj, key);
83
+ }
84
+ return result;
85
+ },
86
+ deleteProperty(obj, key) {
87
+ const hadKey = key in obj;
88
+ const result = Reflect.deleteProperty(obj, key);
89
+ if (hadKey) {
90
+ trigger(obj, key);
91
+ }
92
+ return result;
93
+ }
94
+ });
95
+ }
96
+ /**
97
+ * Track a dependency: the active effect depends on target[key].
98
+ *
99
+ * @internal
100
+ */
101
+ function track(target, key) {
102
+ if (!activeEffect)
103
+ return;
104
+ let depsMap = targetMap.get(target);
105
+ if (!depsMap) {
106
+ depsMap = new Map();
107
+ targetMap.set(target, depsMap);
108
+ }
109
+ let deps = depsMap.get(key);
110
+ if (!deps) {
111
+ deps = new Set();
112
+ depsMap.set(key, deps);
113
+ }
114
+ deps.add(activeEffect);
115
+ let trackedDeps = effectDeps.get(activeEffect);
116
+ if (!trackedDeps) {
117
+ trackedDeps = new Set();
118
+ effectDeps.set(activeEffect, trackedDeps);
119
+ }
120
+ trackedDeps.add(deps);
121
+ }
122
+ /**
123
+ * Trigger effects that depend on target[key].
124
+ *
125
+ * @internal
126
+ */
127
+ function trigger(target, key) {
128
+ const depsMap = targetMap.get(target);
129
+ if (!depsMap)
130
+ return;
131
+ const deps = depsMap.get(key);
132
+ if (deps) {
133
+ const effectsToRun = [...deps];
134
+ effectsToRun.forEach(effect => effect());
135
+ }
136
+ }
137
+ /**
138
+ * Create a reactive effect.
139
+ *
140
+ * @remarks
141
+ * The function runs immediately, tracking dependencies.
142
+ * It re-runs automatically whenever those dependencies change.
143
+ *
144
+ * @param fn - The effect function to run
145
+ * @returns A cleanup function to stop the effect
146
+ *
147
+ * @example
148
+ * ```ts
149
+ * const state = reactive({ count: 0 });
150
+ * const stop = effect(() => {
151
+ * console.log('Count:', state.count);
152
+ * });
153
+ * state.count = 1; // logs: Count: 1
154
+ * stop(); // effect no longer runs
155
+ * ```
156
+ */
157
+ export function effect(fn) {
158
+ const run = () => {
159
+ cleanup(run);
160
+ activeEffect = run;
161
+ fn();
162
+ activeEffect = null;
163
+ };
164
+ run();
165
+ const stopFn = () => cleanup(run);
166
+ registerWithScope(stopFn);
167
+ return stopFn;
168
+ }
169
+ /**
170
+ * Remove an effect from all dependency sets.
171
+ *
172
+ * @internal
173
+ */
174
+ function cleanup(effectFn) {
175
+ const trackedDeps = effectDeps.get(effectFn);
176
+ if (trackedDeps) {
177
+ for (const deps of trackedDeps) {
178
+ deps.delete(effectFn);
179
+ }
180
+ trackedDeps.clear();
181
+ }
182
+ }
183
+ /**
184
+ * Create a child reactive scope.
185
+ *
186
+ * @remarks
187
+ * Used by structural directives like c-for to create per-item contexts.
188
+ * The child scope inherits from the parent, with additions taking precedence.
189
+ *
190
+ * @typeParam T - Parent object type
191
+ * @param parent - The parent reactive object
192
+ * @param additions - Additional properties for this scope
193
+ * @returns A new reactive scope that inherits from parent
194
+ *
195
+ * @example
196
+ * ```ts
197
+ * const parent = reactive({ items: [1, 2, 3] });
198
+ * const child = createScope(parent, { item: 1, index: 0 });
199
+ * child.item; // 1
200
+ * child.items; // [1, 2, 3] (from parent)
201
+ * ```
202
+ */
203
+ export function createScope(parent, additions) {
204
+ const scope = reactive({ ...additions });
205
+ return new Proxy(scope, {
206
+ get(target, key, receiver) {
207
+ if (key in target) {
208
+ return Reflect.get(target, key, receiver);
209
+ }
210
+ return parent[key];
211
+ },
212
+ set(target, key, value, receiver) {
213
+ if (key in target) {
214
+ return Reflect.set(target, key, value, receiver);
215
+ }
216
+ return Reflect.set(parent, key, value);
217
+ }
218
+ });
219
+ }
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Scope management for element state.
3
+ *
4
+ * @packageDocumentation
5
+ */
6
+ import { Directive, DirectiveOptions } from './types.js';
7
+ /**
8
+ * Create a new scope for an element.
9
+ *
10
+ * @param el - The element to create scope for
11
+ * @param parentScope - Optional parent scope to inherit from via prototype
12
+ * @returns The new reactive scope
13
+ */
14
+ export declare function createElementScope(el: Element, parentScope?: Record<string, unknown>): Record<string, unknown>;
15
+ /**
16
+ * Get the scope for an element.
17
+ *
18
+ * @param el - The element
19
+ * @returns The element's scope, or undefined if none
20
+ */
21
+ export declare function getElementScope(el: Element): Record<string, unknown> | undefined;
22
+ /**
23
+ * Find the nearest ancestor scope by walking up the DOM tree.
24
+ *
25
+ * @param el - The element to start from
26
+ * @param includeSelf - Whether to check the element itself (default: false)
27
+ * @returns The nearest scope, or undefined if none found
28
+ */
29
+ export declare function findParentScope(el: Element, includeSelf?: boolean): Record<string, unknown> | undefined;
30
+ /**
31
+ * Remove scope for an element (cleanup).
32
+ *
33
+ * @param el - The element
34
+ */
35
+ export declare function removeElementScope(el: Element): void;
36
+ /**
37
+ * Register a directive as a custom element.
38
+ *
39
+ * @param name - The custom element name (must contain hyphen)
40
+ * @param fn - The directive function
41
+ * @param options - Directive options
42
+ */
43
+ export declare function registerDirectiveElement(name: string, fn: Directive<any>, options: DirectiveOptions): void;
package/dist/scope.js ADDED
@@ -0,0 +1,112 @@
1
+ /**
2
+ * Scope management for element state.
3
+ *
4
+ * @packageDocumentation
5
+ */
6
+ import { reactive } from './reactivity.js';
7
+ import { createContext } from './context.js';
8
+ import { Mode } from './types.js';
9
+ import { getInjectables } from './inject.js';
10
+ /** WeakMap to store element scopes */
11
+ const elementScopes = new WeakMap();
12
+ /**
13
+ * Create a new scope for an element.
14
+ *
15
+ * @param el - The element to create scope for
16
+ * @param parentScope - Optional parent scope to inherit from via prototype
17
+ * @returns The new reactive scope
18
+ */
19
+ export function createElementScope(el, parentScope) {
20
+ let scope;
21
+ if (parentScope) {
22
+ // Create new object with parent as prototype
23
+ scope = reactive(Object.create(parentScope));
24
+ }
25
+ else {
26
+ scope = reactive({});
27
+ }
28
+ elementScopes.set(el, scope);
29
+ return scope;
30
+ }
31
+ /**
32
+ * Get the scope for an element.
33
+ *
34
+ * @param el - The element
35
+ * @returns The element's scope, or undefined if none
36
+ */
37
+ export function getElementScope(el) {
38
+ return elementScopes.get(el);
39
+ }
40
+ /**
41
+ * Find the nearest ancestor scope by walking up the DOM tree.
42
+ *
43
+ * @param el - The element to start from
44
+ * @param includeSelf - Whether to check the element itself (default: false)
45
+ * @returns The nearest scope, or undefined if none found
46
+ */
47
+ export function findParentScope(el, includeSelf = false) {
48
+ let current = includeSelf ? el : el.parentElement;
49
+ while (current) {
50
+ const scope = elementScopes.get(current);
51
+ if (scope) {
52
+ return scope;
53
+ }
54
+ current = current.parentElement;
55
+ }
56
+ return undefined;
57
+ }
58
+ /**
59
+ * Remove scope for an element (cleanup).
60
+ *
61
+ * @param el - The element
62
+ */
63
+ export function removeElementScope(el) {
64
+ elementScopes.delete(el);
65
+ }
66
+ /**
67
+ * Register a directive as a custom element.
68
+ *
69
+ * @param name - The custom element name (must contain hyphen)
70
+ * @param fn - The directive function
71
+ * @param options - Directive options
72
+ */
73
+ export function registerDirectiveElement(name,
74
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
75
+ fn, options) {
76
+ // Don't re-register if already defined
77
+ if (customElements.get(name)) {
78
+ return;
79
+ }
80
+ customElements.define(name, class extends HTMLElement {
81
+ connectedCallback() {
82
+ // Find parent scope for prototype chain
83
+ const parentScope = findParentScope(this);
84
+ // Create this element's scope
85
+ const scope = createElementScope(this, parentScope);
86
+ // Create context for expression evaluation
87
+ const ctx = createContext(Mode.CLIENT, scope);
88
+ // Resolve dependencies and call directive
89
+ const inject = getInjectables(fn);
90
+ const args = inject.map((dep) => {
91
+ switch (dep) {
92
+ case '$element':
93
+ return this;
94
+ case '$state':
95
+ return scope;
96
+ case '$eval':
97
+ return ctx.eval.bind(ctx);
98
+ default:
99
+ return undefined;
100
+ }
101
+ });
102
+ const result = fn(...args);
103
+ // Handle async directives
104
+ if (result instanceof Promise) {
105
+ result.catch(err => console.error(`Error in ${name}:`, err));
106
+ }
107
+ }
108
+ disconnectedCallback() {
109
+ removeElementScope(this);
110
+ }
111
+ });
112
+ }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Server-side rendering utilities.
3
+ *
4
+ * @packageDocumentation
5
+ */
6
+ export { render, registerDirective } from './render.js';
7
+ export type { DirectiveRegistry } from './render.js';
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Server-side rendering utilities.
3
+ *
4
+ * @packageDocumentation
5
+ */
6
+ export { render, registerDirective } from './render.js';
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Server-side rendering with MutationObserver-based directive indexing.
3
+ *
4
+ * @packageDocumentation
5
+ */
6
+ import { Directive } from '../types.js';
7
+ /**
8
+ * Registry of directives by name.
9
+ */
10
+ export type DirectiveRegistry = Map<string, Directive>;
11
+ /**
12
+ * Service registry for dependency injection.
13
+ */
14
+ export type ServiceRegistry = Map<string, unknown>;
15
+ /**
16
+ * Register a directive in the registry.
17
+ *
18
+ * @remarks
19
+ * Invalidates the cached selector so it will be rebuilt on next render.
20
+ *
21
+ * @param registry - The directive registry
22
+ * @param name - Directive name (without c- prefix)
23
+ * @param fn - The directive function
24
+ */
25
+ export declare function registerDirective(registry: DirectiveRegistry, name: string, fn: Directive): void;
26
+ /**
27
+ * Register a service for dependency injection.
28
+ *
29
+ * @param name - Service name (used in $inject arrays)
30
+ * @param service - The service instance
31
+ */
32
+ export declare function registerService(name: string, service: unknown): void;
33
+ /**
34
+ * Render HTML with directives on the server.
35
+ *
36
+ * @remarks
37
+ * Uses MutationObserver to index elements with directive attributes
38
+ * as they are parsed, then executes directives to produce the final HTML.
39
+ * Directive attributes are preserved in output for client hydration.
40
+ * Directives are processed in tree order (parents before children),
41
+ * with priority used only for multiple directives on the same element.
42
+ *
43
+ * @param html - The HTML template string
44
+ * @param state - The state object to use for expression evaluation
45
+ * @param registry - The directive registry
46
+ * @returns The rendered HTML string
47
+ *
48
+ * @example
49
+ * ```ts
50
+ * const registry = new Map();
51
+ * registry.set('text', textDirective);
52
+ *
53
+ * const html = await render(
54
+ * '<span c-text="user.name"></span>',
55
+ * { user: { name: 'Alice' } },
56
+ * registry
57
+ * );
58
+ * // '<span c-text="user.name">Alice</span>'
59
+ * ```
60
+ */
61
+ export declare function render(html: string, state: Record<string, unknown>, registry: DirectiveRegistry): Promise<string>;
@@ -0,0 +1,243 @@
1
+ /**
2
+ * Server-side rendering with MutationObserver-based directive indexing.
3
+ *
4
+ * @packageDocumentation
5
+ */
6
+ import { parseHTML } from 'linkedom';
7
+ import { Mode, DirectivePriority } from '../types.js';
8
+ import { createContext } from '../context.js';
9
+ import { processNativeSlot } from '../directives/slot.js';
10
+ import { getLocalState, registerProvider, resolveFromProviders, resolveFromDIProviders } from '../providers.js';
11
+ import { FOR_PROCESSED_ATTR, FOR_TEMPLATE_ATTR } from '../directives/for.js';
12
+ import { IF_PROCESSED_ATTR } from '../directives/if.js';
13
+ /** Registered services */
14
+ let services = new Map();
15
+ const selectorCache = new WeakMap();
16
+ /**
17
+ * Build a CSS selector for all registered directives.
18
+ *
19
+ * @internal
20
+ */
21
+ function getSelector(registry) {
22
+ let selector = selectorCache.get(registry);
23
+ if (!selector) {
24
+ const directiveSelectors = [...registry.keys()].map(n => `[c-${n}]`);
25
+ // Also match native <slot> elements
26
+ directiveSelectors.push('slot');
27
+ selector = directiveSelectors.join(',');
28
+ selectorCache.set(registry, selector);
29
+ }
30
+ return selector;
31
+ }
32
+ /**
33
+ * Register a directive in the registry.
34
+ *
35
+ * @remarks
36
+ * Invalidates the cached selector so it will be rebuilt on next render.
37
+ *
38
+ * @param registry - The directive registry
39
+ * @param name - Directive name (without c- prefix)
40
+ * @param fn - The directive function
41
+ */
42
+ export function registerDirective(registry, name, fn) {
43
+ registry.set(name, fn);
44
+ selectorCache.delete(registry);
45
+ }
46
+ /**
47
+ * Register a service for dependency injection.
48
+ *
49
+ * @param name - Service name (used in $inject arrays)
50
+ * @param service - The service instance
51
+ */
52
+ export function registerService(name, service) {
53
+ services.set(name, service);
54
+ }
55
+ /**
56
+ * Resolve dependencies for a directive based on its $inject array.
57
+ *
58
+ * @internal
59
+ */
60
+ function resolveDependencies(directive, expr, el, ctx, rootState) {
61
+ const inject = directive.$inject ?? ['$expr', '$element', '$eval'];
62
+ return inject.map(name => {
63
+ switch (name) {
64
+ case '$expr':
65
+ return expr;
66
+ case '$element':
67
+ return el;
68
+ case '$eval':
69
+ return ctx.eval.bind(ctx);
70
+ case '$state':
71
+ return getLocalState(el) ?? rootState;
72
+ case '$rootState':
73
+ return rootState;
74
+ case '$mode':
75
+ return Mode.SERVER;
76
+ default: {
77
+ // Look up in ancestor DI providers first (provide option)
78
+ const diProvided = resolveFromDIProviders(el, name);
79
+ if (diProvided !== undefined) {
80
+ return diProvided;
81
+ }
82
+ // Look up in global services registry
83
+ const service = services.get(name);
84
+ if (service !== undefined) {
85
+ return service;
86
+ }
87
+ // Look up in ancestor context providers ($context)
88
+ const contextProvided = resolveFromProviders(el, name);
89
+ if (contextProvided !== undefined) {
90
+ return contextProvided;
91
+ }
92
+ throw new Error(`Unknown injectable: ${name}`);
93
+ }
94
+ }
95
+ });
96
+ }
97
+ /**
98
+ * Render HTML with directives on the server.
99
+ *
100
+ * @remarks
101
+ * Uses MutationObserver to index elements with directive attributes
102
+ * as they are parsed, then executes directives to produce the final HTML.
103
+ * Directive attributes are preserved in output for client hydration.
104
+ * Directives are processed in tree order (parents before children),
105
+ * with priority used only for multiple directives on the same element.
106
+ *
107
+ * @param html - The HTML template string
108
+ * @param state - The state object to use for expression evaluation
109
+ * @param registry - The directive registry
110
+ * @returns The rendered HTML string
111
+ *
112
+ * @example
113
+ * ```ts
114
+ * const registry = new Map();
115
+ * registry.set('text', textDirective);
116
+ *
117
+ * const html = await render(
118
+ * '<span c-text="user.name"></span>',
119
+ * { user: { name: 'Alice' } },
120
+ * registry
121
+ * );
122
+ * // '<span c-text="user.name">Alice</span>'
123
+ * ```
124
+ */
125
+ export async function render(html, state, registry) {
126
+ const { document, MutationObserver } = parseHTML('<!DOCTYPE html><html><body></body></html>');
127
+ const index = [];
128
+ const selector = getSelector(registry);
129
+ const observer = new MutationObserver((mutations) => {
130
+ for (const mutation of mutations) {
131
+ for (const node of mutation.addedNodes) {
132
+ if (node.nodeType !== 1)
133
+ continue;
134
+ const el = node;
135
+ const matches = el.matches(selector) ? [el] : [];
136
+ const descendants = [...el.querySelectorAll(selector)];
137
+ for (const match of [...matches, ...descendants]) {
138
+ // Handle native <slot> elements
139
+ if (match.tagName === 'SLOT') {
140
+ index.push({
141
+ el: match,
142
+ name: 'slot',
143
+ directive: null,
144
+ expr: '',
145
+ priority: DirectivePriority.NORMAL,
146
+ isNativeSlot: true
147
+ });
148
+ continue;
149
+ }
150
+ for (const [name, directive] of registry) {
151
+ const attr = match.getAttribute(`c-${name}`);
152
+ if (attr !== null) {
153
+ index.push({
154
+ el: match,
155
+ name,
156
+ directive,
157
+ expr: attr,
158
+ priority: directive.priority ?? DirectivePriority.NORMAL
159
+ });
160
+ }
161
+ }
162
+ }
163
+ }
164
+ }
165
+ });
166
+ observer.observe(document.body, { childList: true, subtree: true });
167
+ document.body.innerHTML = html;
168
+ await new Promise(r => setTimeout(r, 0));
169
+ const ctx = createContext(Mode.SERVER, state);
170
+ const processed = new Set();
171
+ // Process directives in rounds until no new elements are added
172
+ let hasMore = true;
173
+ while (hasMore) {
174
+ // Group by element, preserving tree order from the index
175
+ const elementOrder = [];
176
+ const byElement = new Map();
177
+ for (const item of index) {
178
+ if (processed.has(item.el))
179
+ continue;
180
+ if (!byElement.has(item.el)) {
181
+ elementOrder.push(item.el);
182
+ byElement.set(item.el, []);
183
+ }
184
+ byElement.get(item.el).push(item);
185
+ }
186
+ if (elementOrder.length === 0) {
187
+ hasMore = false;
188
+ break;
189
+ }
190
+ // Process elements in tree order
191
+ for (const el of elementOrder) {
192
+ // Skip elements that were removed (e.g., by c-for cloning)
193
+ if (!el.isConnected) {
194
+ processed.add(el);
195
+ continue;
196
+ }
197
+ // Skip elements already processed by structural directives (c-for, c-if)
198
+ // These elements have their own scoped processing
199
+ if (el.hasAttribute(FOR_PROCESSED_ATTR) || el.hasAttribute(IF_PROCESSED_ATTR)) {
200
+ processed.add(el);
201
+ continue;
202
+ }
203
+ // Skip template elements with c-for - these are template wrappers created by c-for
204
+ // and should not be processed as directives (they're for client hydration)
205
+ if (el.tagName === 'TEMPLATE' && el.hasAttribute('c-for')) {
206
+ processed.add(el);
207
+ continue;
208
+ }
209
+ // Skip template content elements - these are inside template wrappers
210
+ // and their directives are processed by c-for when rendering items
211
+ if (el.hasAttribute(FOR_TEMPLATE_ATTR)) {
212
+ processed.add(el);
213
+ continue;
214
+ }
215
+ processed.add(el);
216
+ const directives = byElement.get(el);
217
+ // Sort directives on this element by priority (higher first)
218
+ directives.sort((a, b) => b.priority - a.priority);
219
+ for (const item of directives) {
220
+ // Check if element was disconnected by a previous directive (e.g., c-for replacing it)
221
+ if (!item.el.isConnected) {
222
+ break;
223
+ }
224
+ if (item.isNativeSlot) {
225
+ processNativeSlot(item.el);
226
+ }
227
+ else {
228
+ const args = resolveDependencies(item.directive, item.expr, item.el, ctx, state);
229
+ await item.directive(...args);
230
+ // Register as context provider if directive declares $context
231
+ if (item.directive.$context?.length) {
232
+ const localState = getLocalState(item.el);
233
+ registerProvider(item.el, item.directive, localState);
234
+ }
235
+ }
236
+ }
237
+ // Let observer catch new elements
238
+ await new Promise(r => setTimeout(r, 0));
239
+ }
240
+ }
241
+ observer.disconnect();
242
+ return document.body.innerHTML;
243
+ }