gonia 0.3.0 → 0.3.2
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/dist/client/hydrate.js +65 -6
- package/dist/directive-utils.d.ts +50 -0
- package/dist/directive-utils.js +101 -0
- package/dist/server/render.js +95 -33
- package/package.json +1 -1
package/dist/client/hydrate.js
CHANGED
|
@@ -12,6 +12,7 @@ import { findParentScope, createElementScope, getElementScope } from '../scope.j
|
|
|
12
12
|
import { resolveDependencies as resolveInjectables } from '../inject.js';
|
|
13
13
|
import { resolveContext } from '../context-registry.js';
|
|
14
14
|
import { effect } from '../reactivity.js';
|
|
15
|
+
import { applyAssigns, directiveNeedsScope } from '../directive-utils.js';
|
|
15
16
|
// Built-in directives
|
|
16
17
|
import { text } from '../directives/text.js';
|
|
17
18
|
import { show } from '../directives/show.js';
|
|
@@ -56,15 +57,22 @@ function getDefaultRegistry() {
|
|
|
56
57
|
function getSelector(registry) {
|
|
57
58
|
if (!cachedSelector) {
|
|
58
59
|
const directiveSelectors = [];
|
|
60
|
+
// Include directives from passed registry
|
|
59
61
|
for (const name of registry.keys()) {
|
|
60
62
|
directiveSelectors.push(`[g-${name}]`);
|
|
61
63
|
}
|
|
64
|
+
// Include directives from global registry
|
|
65
|
+
for (const name of getDirectiveNames()) {
|
|
66
|
+
directiveSelectors.push(`[${name}]`);
|
|
67
|
+
}
|
|
62
68
|
// Also match native <slot> elements
|
|
63
69
|
directiveSelectors.push('slot');
|
|
64
70
|
// Match template placeholders from SSR (g-if with false condition)
|
|
65
71
|
directiveSelectors.push('template[data-g-if]');
|
|
66
72
|
// Match g-scope for inline scope initialization
|
|
67
73
|
directiveSelectors.push('[g-scope]');
|
|
74
|
+
// Match g-bind:* for attribute bindings
|
|
75
|
+
directiveSelectors.push('[g-bind\\:class]');
|
|
68
76
|
cachedSelector = directiveSelectors.join(',');
|
|
69
77
|
}
|
|
70
78
|
return cachedSelector;
|
|
@@ -76,10 +84,10 @@ function getSelector(registry) {
|
|
|
76
84
|
*/
|
|
77
85
|
function getDirectivesForElement(el, registry) {
|
|
78
86
|
const directives = [];
|
|
87
|
+
// Check local registry (built-in directives with g- prefix)
|
|
79
88
|
for (const [name, directive] of registry) {
|
|
80
89
|
const attr = el.getAttribute(`g-${name}`);
|
|
81
90
|
if (attr !== null) {
|
|
82
|
-
// Look up options from the global directive registry
|
|
83
91
|
const registration = getDirective(`g-${name}`);
|
|
84
92
|
directives.push({
|
|
85
93
|
name,
|
|
@@ -89,6 +97,24 @@ function getDirectivesForElement(el, registry) {
|
|
|
89
97
|
});
|
|
90
98
|
}
|
|
91
99
|
}
|
|
100
|
+
// Check global registry (custom directives)
|
|
101
|
+
for (const name of getDirectiveNames()) {
|
|
102
|
+
// Skip if already matched via local registry
|
|
103
|
+
if (directives.some(d => `g-${d.name}` === name))
|
|
104
|
+
continue;
|
|
105
|
+
const attr = el.getAttribute(name);
|
|
106
|
+
if (attr !== null) {
|
|
107
|
+
const registration = getDirective(name);
|
|
108
|
+
if (registration?.fn) {
|
|
109
|
+
directives.push({
|
|
110
|
+
name: name.replace(/^g-/, ''),
|
|
111
|
+
directive: registration.fn,
|
|
112
|
+
expr: attr,
|
|
113
|
+
using: registration.options.using
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
92
118
|
// Sort by priority (higher first)
|
|
93
119
|
directives.sort((a, b) => {
|
|
94
120
|
const priorityA = a.directive.priority ?? DirectivePriority.NORMAL;
|
|
@@ -199,8 +225,35 @@ function processElement(el, registry) {
|
|
|
199
225
|
// Skip if nothing to process
|
|
200
226
|
if (directives.length === 0 && !hasScopeAttr && !hasBindAttrs)
|
|
201
227
|
return;
|
|
202
|
-
|
|
203
|
-
|
|
228
|
+
// Check if any directive needs a scope
|
|
229
|
+
let scope = findParentScope(el, true) ?? {};
|
|
230
|
+
let directiveCreatedScope = false;
|
|
231
|
+
// Collect unique directive names for conflict detection
|
|
232
|
+
const directiveNameSet = new Set();
|
|
233
|
+
for (const { name } of directives) {
|
|
234
|
+
const fullName = `g-${name}`;
|
|
235
|
+
const isNew = !directiveNameSet.has(fullName);
|
|
236
|
+
directiveNameSet.add(fullName);
|
|
237
|
+
// Only process first occurrence
|
|
238
|
+
if (!isNew)
|
|
239
|
+
continue;
|
|
240
|
+
const registration = getDirective(fullName);
|
|
241
|
+
if (!directiveCreatedScope && directiveNeedsScope(fullName)) {
|
|
242
|
+
// Create a new scope that inherits from parent
|
|
243
|
+
scope = createElementScope(el, scope);
|
|
244
|
+
directiveCreatedScope = true;
|
|
245
|
+
}
|
|
246
|
+
// Register DI providers if present
|
|
247
|
+
if (registration?.options.provide) {
|
|
248
|
+
registerDIProviders(el, registration.options.provide);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
// Apply assigns with conflict detection
|
|
252
|
+
if (directiveCreatedScope) {
|
|
253
|
+
applyAssigns(scope, [...directiveNameSet]);
|
|
254
|
+
}
|
|
255
|
+
const ctx = createContext(Mode.CLIENT, scope);
|
|
256
|
+
contextCache.set(el, ctx);
|
|
204
257
|
// Process g-scope first (inline scope initialization)
|
|
205
258
|
if (hasScopeAttr) {
|
|
206
259
|
const scopeAttr = el.getAttribute('g-scope');
|
|
@@ -384,10 +437,16 @@ async function processDirectiveElements() {
|
|
|
384
437
|
if (options.scope) {
|
|
385
438
|
const parentScope = findParentScope(el);
|
|
386
439
|
scope = createElementScope(el, parentScope);
|
|
387
|
-
//
|
|
388
|
-
|
|
389
|
-
|
|
440
|
+
// Collect unique directive names on this element for conflict detection
|
|
441
|
+
const directiveNameSet = new Set([name]);
|
|
442
|
+
for (const attr of el.attributes) {
|
|
443
|
+
const attrReg = getDirective(attr.name);
|
|
444
|
+
if (attrReg) {
|
|
445
|
+
directiveNameSet.add(attr.name);
|
|
446
|
+
}
|
|
390
447
|
}
|
|
448
|
+
// Apply assigns with conflict detection
|
|
449
|
+
applyAssigns(scope, [...directiveNameSet]);
|
|
391
450
|
}
|
|
392
451
|
else {
|
|
393
452
|
scope = findParentScope(el, true) ?? {};
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared utilities for directive processing (client and server).
|
|
3
|
+
*
|
|
4
|
+
* @packageDocumentation
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Information about an assign conflict.
|
|
8
|
+
*/
|
|
9
|
+
export interface AssignConflict {
|
|
10
|
+
key: string;
|
|
11
|
+
directives: string[];
|
|
12
|
+
priorities: number[];
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Result of resolving assigns from multiple directives.
|
|
16
|
+
*/
|
|
17
|
+
export interface ResolvedAssigns {
|
|
18
|
+
/** The merged assign values (higher priority wins) */
|
|
19
|
+
values: Record<string, unknown>;
|
|
20
|
+
/** Warnings for different-priority conflicts */
|
|
21
|
+
warnings: string[];
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Resolve assign values from multiple directives on the same element.
|
|
25
|
+
*
|
|
26
|
+
* @param directiveNames - Names of directives on the element
|
|
27
|
+
* @throws Error if same-priority directives conflict on an assign key
|
|
28
|
+
* @returns Resolved assigns and any warnings
|
|
29
|
+
*/
|
|
30
|
+
export declare function resolveAssigns(directiveNames: string[]): ResolvedAssigns;
|
|
31
|
+
/**
|
|
32
|
+
* Apply resolved assigns to a scope, logging any warnings.
|
|
33
|
+
*
|
|
34
|
+
* @param scope - The scope to apply assigns to
|
|
35
|
+
* @param directiveNames - Names of directives on the element
|
|
36
|
+
* @returns The scope with assigns applied
|
|
37
|
+
*/
|
|
38
|
+
export declare function applyAssigns(scope: Record<string, unknown>, directiveNames: string[]): Record<string, unknown>;
|
|
39
|
+
/**
|
|
40
|
+
* Check if a directive should create/use a scope based on its options.
|
|
41
|
+
*/
|
|
42
|
+
export declare function directiveNeedsScope(name: string): boolean;
|
|
43
|
+
/**
|
|
44
|
+
* Get directive options with defaults.
|
|
45
|
+
*/
|
|
46
|
+
export declare function getDirectiveOptions(name: string): import("./types.js").DirectiveOptions;
|
|
47
|
+
/**
|
|
48
|
+
* Get directive priority.
|
|
49
|
+
*/
|
|
50
|
+
export declare function getDirectivePriority(name: string): number;
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared utilities for directive processing (client and server).
|
|
3
|
+
*
|
|
4
|
+
* @packageDocumentation
|
|
5
|
+
*/
|
|
6
|
+
import { getDirective, DirectivePriority } from './types.js';
|
|
7
|
+
/**
|
|
8
|
+
* Resolve assign values from multiple directives on the same element.
|
|
9
|
+
*
|
|
10
|
+
* @param directiveNames - Names of directives on the element
|
|
11
|
+
* @throws Error if same-priority directives conflict on an assign key
|
|
12
|
+
* @returns Resolved assigns and any warnings
|
|
13
|
+
*/
|
|
14
|
+
export function resolveAssigns(directiveNames) {
|
|
15
|
+
const assignsByKey = new Map();
|
|
16
|
+
// Collect all assigns grouped by key
|
|
17
|
+
for (const name of directiveNames) {
|
|
18
|
+
const registration = getDirective(name);
|
|
19
|
+
if (!registration?.options.assign)
|
|
20
|
+
continue;
|
|
21
|
+
const priority = registration.fn?.priority ?? DirectivePriority.NORMAL;
|
|
22
|
+
for (const [key, value] of Object.entries(registration.options.assign)) {
|
|
23
|
+
if (!assignsByKey.has(key)) {
|
|
24
|
+
assignsByKey.set(key, []);
|
|
25
|
+
}
|
|
26
|
+
assignsByKey.get(key).push({ directive: name, priority, value });
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
const values = {};
|
|
30
|
+
const warnings = [];
|
|
31
|
+
// Check for conflicts and resolve
|
|
32
|
+
for (const [key, sources] of assignsByKey) {
|
|
33
|
+
if (sources.length === 1) {
|
|
34
|
+
values[key] = sources[0].value;
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
// Group by priority
|
|
38
|
+
const byPriority = new Map();
|
|
39
|
+
for (const source of sources) {
|
|
40
|
+
if (!byPriority.has(source.priority)) {
|
|
41
|
+
byPriority.set(source.priority, []);
|
|
42
|
+
}
|
|
43
|
+
byPriority.get(source.priority).push(source);
|
|
44
|
+
}
|
|
45
|
+
// Check for same-priority conflicts
|
|
46
|
+
for (const [priority, group] of byPriority) {
|
|
47
|
+
if (group.length > 1) {
|
|
48
|
+
const names = group.map(s => s.directive).join(', ');
|
|
49
|
+
throw new Error(`Conflicting assign key "${key}" at same priority (${priority}) between directives: ${names}`);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
// Different priorities - highest wins, emit warning
|
|
53
|
+
const sorted = sources.sort((a, b) => b.priority - a.priority);
|
|
54
|
+
const winner = sorted[0];
|
|
55
|
+
const losers = sorted.slice(1);
|
|
56
|
+
values[key] = winner.value;
|
|
57
|
+
for (const loser of losers) {
|
|
58
|
+
warnings.push(`Directive "${winner.directive}" (priority ${winner.priority}) overrides assign key "${key}" from "${loser.directive}" (priority ${loser.priority})`);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return { values, warnings };
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Apply resolved assigns to a scope, logging any warnings.
|
|
65
|
+
*
|
|
66
|
+
* @param scope - The scope to apply assigns to
|
|
67
|
+
* @param directiveNames - Names of directives on the element
|
|
68
|
+
* @returns The scope with assigns applied
|
|
69
|
+
*/
|
|
70
|
+
export function applyAssigns(scope, directiveNames) {
|
|
71
|
+
const { values, warnings } = resolveAssigns(directiveNames);
|
|
72
|
+
for (const warning of warnings) {
|
|
73
|
+
console.warn(`[gonia] ${warning}`);
|
|
74
|
+
}
|
|
75
|
+
Object.assign(scope, values);
|
|
76
|
+
return scope;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Check if a directive should create/use a scope based on its options.
|
|
80
|
+
*/
|
|
81
|
+
export function directiveNeedsScope(name) {
|
|
82
|
+
const registration = getDirective(name);
|
|
83
|
+
if (!registration)
|
|
84
|
+
return false;
|
|
85
|
+
const { options, fn } = registration;
|
|
86
|
+
return !!(options.scope || options.assign || fn?.$context?.length);
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Get directive options with defaults.
|
|
90
|
+
*/
|
|
91
|
+
export function getDirectiveOptions(name) {
|
|
92
|
+
const registration = getDirective(name);
|
|
93
|
+
return registration?.options ?? {};
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Get directive priority.
|
|
97
|
+
*/
|
|
98
|
+
export function getDirectivePriority(name) {
|
|
99
|
+
const registration = getDirective(name);
|
|
100
|
+
return registration?.fn?.priority ?? DirectivePriority.NORMAL;
|
|
101
|
+
}
|
package/dist/server/render.js
CHANGED
|
@@ -14,6 +14,7 @@ import { IF_PROCESSED_ATTR } from '../directives/if.js';
|
|
|
14
14
|
import { PROCESSED_ATTR } from '../process.js';
|
|
15
15
|
import { resolveDependencies as resolveInjectables } from '../inject.js';
|
|
16
16
|
import { resolveContext } from '../context-registry.js';
|
|
17
|
+
import { applyAssigns, directiveNeedsScope } from '../directive-utils.js';
|
|
17
18
|
/**
|
|
18
19
|
* Decode HTML entities that happy-dom doesn't decode.
|
|
19
20
|
*
|
|
@@ -65,6 +66,22 @@ function getSelector(localRegistry) {
|
|
|
65
66
|
selectors.push('slot');
|
|
66
67
|
// Match g-scope for inline scope initialization (TODO: make prefix configurable)
|
|
67
68
|
selectors.push('[g-scope]');
|
|
69
|
+
// Match common g-bind:* attributes for dynamic binding
|
|
70
|
+
// These need to be indexed so their expressions can be evaluated with proper scope
|
|
71
|
+
selectors.push('[g-bind\\:class]');
|
|
72
|
+
selectors.push('[g-bind\\:style]');
|
|
73
|
+
selectors.push('[g-bind\\:href]');
|
|
74
|
+
selectors.push('[g-bind\\:src]');
|
|
75
|
+
selectors.push('[g-bind\\:id]');
|
|
76
|
+
selectors.push('[g-bind\\:value]');
|
|
77
|
+
selectors.push('[g-bind\\:disabled]');
|
|
78
|
+
selectors.push('[g-bind\\:checked]');
|
|
79
|
+
selectors.push('[g-bind\\:placeholder]');
|
|
80
|
+
selectors.push('[g-bind\\:title]');
|
|
81
|
+
selectors.push('[g-bind\\:alt]');
|
|
82
|
+
selectors.push('[g-bind\\:name]');
|
|
83
|
+
selectors.push('[g-bind\\:type]');
|
|
84
|
+
// Note: Can't do wildcard for data-* attributes in CSS, but hasBindAttributes handles them
|
|
68
85
|
return selectors.join(',');
|
|
69
86
|
}
|
|
70
87
|
/**
|
|
@@ -189,7 +206,23 @@ export async function render(html, state, registry) {
|
|
|
189
206
|
const window = new Window();
|
|
190
207
|
const document = window.document;
|
|
191
208
|
const index = [];
|
|
209
|
+
const indexedDirectives = new Map(); // Track indexed (element, directive) pairs
|
|
192
210
|
const selector = getSelector(registry);
|
|
211
|
+
// Helper to add to index only if not already indexed for this (element, directive) pair
|
|
212
|
+
const addToIndex = (item) => {
|
|
213
|
+
const existing = indexedDirectives.get(item.el);
|
|
214
|
+
if (existing?.has(item.name)) {
|
|
215
|
+
return false; // Already indexed
|
|
216
|
+
}
|
|
217
|
+
if (!existing) {
|
|
218
|
+
indexedDirectives.set(item.el, new Set([item.name]));
|
|
219
|
+
}
|
|
220
|
+
else {
|
|
221
|
+
existing.add(item.name);
|
|
222
|
+
}
|
|
223
|
+
index.push(item);
|
|
224
|
+
return true;
|
|
225
|
+
};
|
|
193
226
|
const observer = new window.MutationObserver((mutations) => {
|
|
194
227
|
for (const mutation of mutations) {
|
|
195
228
|
for (const node of mutation.addedNodes) {
|
|
@@ -205,7 +238,7 @@ export async function render(html, state, registry) {
|
|
|
205
238
|
}
|
|
206
239
|
// Handle native <slot> elements
|
|
207
240
|
if (match.tagName === 'SLOT') {
|
|
208
|
-
|
|
241
|
+
addToIndex({
|
|
209
242
|
el: match,
|
|
210
243
|
name: 'slot',
|
|
211
244
|
directive: null,
|
|
@@ -226,7 +259,7 @@ export async function render(html, state, registry) {
|
|
|
226
259
|
}
|
|
227
260
|
}
|
|
228
261
|
if (!hasDirective) {
|
|
229
|
-
|
|
262
|
+
addToIndex({
|
|
230
263
|
el: match,
|
|
231
264
|
name: 'scope',
|
|
232
265
|
directive: null,
|
|
@@ -236,6 +269,27 @@ export async function render(html, state, registry) {
|
|
|
236
269
|
});
|
|
237
270
|
}
|
|
238
271
|
}
|
|
272
|
+
// Handle g-bind:* elements that don't have other directives
|
|
273
|
+
// Add a placeholder so they get processed for dynamic attribute binding
|
|
274
|
+
if (hasBindAttributes(match)) {
|
|
275
|
+
let hasDirective = false;
|
|
276
|
+
for (const name of getDirectiveNames()) {
|
|
277
|
+
if (match.hasAttribute(name)) {
|
|
278
|
+
hasDirective = true;
|
|
279
|
+
break;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
if (!hasDirective && !match.hasAttribute('g-scope')) {
|
|
283
|
+
addToIndex({
|
|
284
|
+
el: match,
|
|
285
|
+
name: 'bind',
|
|
286
|
+
directive: null,
|
|
287
|
+
expr: '',
|
|
288
|
+
priority: DirectivePriority.NORMAL,
|
|
289
|
+
isNativeSlot: false
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
}
|
|
239
293
|
// Check all registered directives from global registry
|
|
240
294
|
const tagName = match.tagName.toLowerCase();
|
|
241
295
|
for (const name of getDirectiveNames()) {
|
|
@@ -246,7 +300,7 @@ export async function render(html, state, registry) {
|
|
|
246
300
|
// Check if this is a custom element directive (tag name matches)
|
|
247
301
|
if (tagName === name) {
|
|
248
302
|
if (options.template || options.scope || options.provide || options.using) {
|
|
249
|
-
|
|
303
|
+
addToIndex({
|
|
250
304
|
el: match,
|
|
251
305
|
name,
|
|
252
306
|
directive: fn,
|
|
@@ -260,7 +314,7 @@ export async function render(html, state, registry) {
|
|
|
260
314
|
// Check if this is an attribute directive
|
|
261
315
|
const attr = match.getAttribute(name);
|
|
262
316
|
if (attr !== null) {
|
|
263
|
-
|
|
317
|
+
addToIndex({
|
|
264
318
|
el: match,
|
|
265
319
|
name,
|
|
266
320
|
directive: fn,
|
|
@@ -279,7 +333,7 @@ export async function render(html, state, registry) {
|
|
|
279
333
|
const fullName = `g-${name}`;
|
|
280
334
|
if (getDirective(fullName))
|
|
281
335
|
continue;
|
|
282
|
-
|
|
336
|
+
addToIndex({
|
|
283
337
|
el: match,
|
|
284
338
|
name,
|
|
285
339
|
directive,
|
|
@@ -354,10 +408,13 @@ export async function render(html, state, registry) {
|
|
|
354
408
|
}
|
|
355
409
|
}
|
|
356
410
|
// Process g-bind:* attributes (dynamic attribute binding)
|
|
411
|
+
// Use the nearest ancestor scope for evaluation
|
|
412
|
+
const bindScope = findServerScope(el, state);
|
|
413
|
+
const bindCtx = createContext(Mode.SERVER, bindScope);
|
|
357
414
|
for (const attr of [...el.attributes]) {
|
|
358
415
|
if (attr.name.startsWith('g-bind:')) {
|
|
359
416
|
const targetAttr = attr.name.slice('g-bind:'.length);
|
|
360
|
-
const value =
|
|
417
|
+
const value = bindCtx.eval(decodeHTMLEntities(attr.value));
|
|
361
418
|
if (value === null || value === undefined) {
|
|
362
419
|
el.removeAttribute(targetAttr);
|
|
363
420
|
}
|
|
@@ -366,6 +423,25 @@ export async function render(html, state, registry) {
|
|
|
366
423
|
}
|
|
367
424
|
}
|
|
368
425
|
}
|
|
426
|
+
// Collect unique directive names for conflict detection
|
|
427
|
+
const directiveNameSet = new Set();
|
|
428
|
+
for (const item of directives) {
|
|
429
|
+
if (!item.isNativeSlot && item.directive !== null) {
|
|
430
|
+
directiveNameSet.add(item.name);
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
const directiveNames = [...directiveNameSet];
|
|
434
|
+
// Check if any directive needs scope - create once if so
|
|
435
|
+
let elementScope = null;
|
|
436
|
+
for (const name of directiveNames) {
|
|
437
|
+
if (directiveNeedsScope(name)) {
|
|
438
|
+
const parentScope = findServerScope(el, state);
|
|
439
|
+
elementScope = createElementScope(el, parentScope);
|
|
440
|
+
// Apply all assigns with conflict detection
|
|
441
|
+
applyAssigns(elementScope, directiveNames);
|
|
442
|
+
break;
|
|
443
|
+
}
|
|
444
|
+
}
|
|
369
445
|
for (const item of directives) {
|
|
370
446
|
// Check if element was disconnected by a previous directive (e.g., g-for replacing it)
|
|
371
447
|
if (!item.el.isConnected) {
|
|
@@ -382,24 +458,13 @@ export async function render(html, state, registry) {
|
|
|
382
458
|
if (!registration)
|
|
383
459
|
continue;
|
|
384
460
|
const { fn, options } = registration;
|
|
385
|
-
//
|
|
386
|
-
|
|
387
|
-
if
|
|
388
|
-
const parentScope = findServerScope(item.el, state);
|
|
389
|
-
scopeState = createElementScope(item.el, parentScope);
|
|
390
|
-
// Apply assigned values to scope
|
|
391
|
-
if (options.assign) {
|
|
392
|
-
Object.assign(scopeState, options.assign);
|
|
393
|
-
}
|
|
394
|
-
}
|
|
395
|
-
else {
|
|
396
|
-
scopeState = findServerScope(item.el, state);
|
|
397
|
-
}
|
|
398
|
-
// 2. Register DI providers if present
|
|
461
|
+
// Use pre-created scope or find existing
|
|
462
|
+
const scopeState = elementScope ?? findServerScope(item.el, state);
|
|
463
|
+
// Register DI providers if present
|
|
399
464
|
if (options.provide) {
|
|
400
465
|
registerDIProviders(item.el, options.provide);
|
|
401
466
|
}
|
|
402
|
-
//
|
|
467
|
+
// Call directive function if present (initializes state)
|
|
403
468
|
if (fn) {
|
|
404
469
|
const config = createServerResolverConfig(item.el, scopeState, state);
|
|
405
470
|
const args = resolveInjectables(fn, item.expr, item.el, ctx.eval.bind(ctx), config, options.using);
|
|
@@ -409,7 +474,7 @@ export async function render(html, state, registry) {
|
|
|
409
474
|
registerProvider(item.el, fn, scopeState);
|
|
410
475
|
}
|
|
411
476
|
}
|
|
412
|
-
//
|
|
477
|
+
// Render template if present
|
|
413
478
|
if (options.template) {
|
|
414
479
|
const attrs = getTemplateAttrs(item.el);
|
|
415
480
|
let html;
|
|
@@ -428,17 +493,14 @@ export async function render(html, state, registry) {
|
|
|
428
493
|
continue;
|
|
429
494
|
}
|
|
430
495
|
else {
|
|
431
|
-
// Attribute directive
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
else {
|
|
440
|
-
// Other directives use nearest ancestor scope or root
|
|
441
|
-
scopeState = findServerScope(item.el, state);
|
|
496
|
+
// Attribute directive - use pre-created scope or find existing
|
|
497
|
+
const scopeState = elementScope ?? findServerScope(item.el, state);
|
|
498
|
+
// Get registration options
|
|
499
|
+
const registration = getDirective(item.name);
|
|
500
|
+
const options = registration?.options ?? {};
|
|
501
|
+
// Register DI providers if present
|
|
502
|
+
if (options.provide) {
|
|
503
|
+
registerDIProviders(item.el, options.provide);
|
|
442
504
|
}
|
|
443
505
|
const config = createServerResolverConfig(item.el, scopeState, state);
|
|
444
506
|
const args = resolveInjectables(item.directive, item.expr, item.el, ctx.eval.bind(ctx), config, item.using);
|