gonia 0.2.1 → 0.2.3
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/directives/slot.js +2 -2
- package/dist/inject.d.ts +9 -6
- package/dist/inject.js +5 -8
- package/dist/server/render.js +76 -9
- package/dist/types.d.ts +5 -5
- package/package.json +1 -1
package/dist/directives/slot.js
CHANGED
|
@@ -70,8 +70,8 @@ export const slot = function slot($expr, $element, $eval, $slotContent) {
|
|
|
70
70
|
transclude();
|
|
71
71
|
}
|
|
72
72
|
};
|
|
73
|
-
slot.$inject = ['$expr', '$element', '$eval'
|
|
74
|
-
directive('g-slot', slot);
|
|
73
|
+
slot.$inject = ['$expr', '$element', '$eval'];
|
|
74
|
+
directive('g-slot', slot, { using: [SlotContentContext] });
|
|
75
75
|
/**
|
|
76
76
|
* Process native <slot> elements.
|
|
77
77
|
*
|
package/dist/inject.d.ts
CHANGED
|
@@ -14,9 +14,10 @@
|
|
|
14
14
|
import type { ContextKey } from './context-registry.js';
|
|
15
15
|
import type { Expression, EvalFn } from './types.js';
|
|
16
16
|
/**
|
|
17
|
-
* An injectable dependency
|
|
17
|
+
* An injectable dependency name.
|
|
18
|
+
* For ContextKey injection, use the `using` option on directive registration.
|
|
18
19
|
*/
|
|
19
|
-
export type Injectable = string
|
|
20
|
+
export type Injectable = string;
|
|
20
21
|
/**
|
|
21
22
|
* Check if a value is a ContextKey.
|
|
22
23
|
*/
|
|
@@ -25,7 +26,7 @@ export declare function isContextKey(value: unknown): value is ContextKey<unknow
|
|
|
25
26
|
* A function with optional `$inject` annotation.
|
|
26
27
|
*/
|
|
27
28
|
interface InjectableFunction extends Function {
|
|
28
|
-
$inject?: readonly
|
|
29
|
+
$inject?: readonly string[];
|
|
29
30
|
}
|
|
30
31
|
/**
|
|
31
32
|
* Get the list of injectable dependencies for a function.
|
|
@@ -43,10 +44,12 @@ interface InjectableFunction extends Function {
|
|
|
43
44
|
* const myDirective = (expr, ctx, el, http, userService) => {};
|
|
44
45
|
* getInjectables(myDirective); // ['expr', 'ctx', 'el', 'http', 'userService']
|
|
45
46
|
*
|
|
46
|
-
* // Production - explicit
|
|
47
|
-
* myDirective.$inject = ['$element',
|
|
48
|
-
* getInjectables(myDirective); // ['$element',
|
|
47
|
+
* // Production - explicit $inject array (survives minification)
|
|
48
|
+
* myDirective.$inject = ['$element', '$scope'];
|
|
49
|
+
* getInjectables(myDirective); // ['$element', '$scope']
|
|
49
50
|
* ```
|
|
51
|
+
*
|
|
52
|
+
* For ContextKey injection, use the `using` option on directive registration.
|
|
50
53
|
*/
|
|
51
54
|
export declare function getInjectables(fn: InjectableFunction): Injectable[];
|
|
52
55
|
/**
|
package/dist/inject.js
CHANGED
|
@@ -33,10 +33,12 @@ export function isContextKey(value) {
|
|
|
33
33
|
* const myDirective = (expr, ctx, el, http, userService) => {};
|
|
34
34
|
* getInjectables(myDirective); // ['expr', 'ctx', 'el', 'http', 'userService']
|
|
35
35
|
*
|
|
36
|
-
* // Production - explicit
|
|
37
|
-
* myDirective.$inject = ['$element',
|
|
38
|
-
* getInjectables(myDirective); // ['$element',
|
|
36
|
+
* // Production - explicit $inject array (survives minification)
|
|
37
|
+
* myDirective.$inject = ['$element', '$scope'];
|
|
38
|
+
* getInjectables(myDirective); // ['$element', '$scope']
|
|
39
39
|
* ```
|
|
40
|
+
*
|
|
41
|
+
* For ContextKey injection, use the `using` option on directive registration.
|
|
40
42
|
*/
|
|
41
43
|
export function getInjectables(fn) {
|
|
42
44
|
if ('$inject' in fn && Array.isArray(fn.$inject)) {
|
|
@@ -85,11 +87,6 @@ function parseFunctionParams(fn) {
|
|
|
85
87
|
export function resolveDependencies(fn, expr, element, evalFn, config, using) {
|
|
86
88
|
const inject = getInjectables(fn);
|
|
87
89
|
const args = inject.map(dep => {
|
|
88
|
-
// Handle ContextKey injection
|
|
89
|
-
if (isContextKey(dep)) {
|
|
90
|
-
return config.resolveContext(dep);
|
|
91
|
-
}
|
|
92
|
-
// Handle string-based injection
|
|
93
90
|
switch (dep) {
|
|
94
91
|
case '$expr':
|
|
95
92
|
return expr;
|
package/dist/server/render.js
CHANGED
|
@@ -7,11 +7,26 @@ import { Window } from 'happy-dom';
|
|
|
7
7
|
import { Mode, DirectivePriority, getDirective } from '../types.js';
|
|
8
8
|
import { createContext } from '../context.js';
|
|
9
9
|
import { processNativeSlot } from '../directives/slot.js';
|
|
10
|
-
import {
|
|
10
|
+
import { registerProvider, resolveFromProviders, resolveFromDIProviders } from '../providers.js';
|
|
11
|
+
import { createElementScope, getElementScope } from '../scope.js';
|
|
11
12
|
import { FOR_PROCESSED_ATTR, FOR_TEMPLATE_ATTR } from '../directives/for.js';
|
|
12
13
|
import { IF_PROCESSED_ATTR } from '../directives/if.js';
|
|
13
14
|
import { resolveDependencies as resolveInjectables } from '../inject.js';
|
|
14
15
|
import { resolveContext } from '../context-registry.js';
|
|
16
|
+
/**
|
|
17
|
+
* Decode HTML entities that happy-dom doesn't decode.
|
|
18
|
+
*
|
|
19
|
+
* @internal
|
|
20
|
+
*/
|
|
21
|
+
function decodeHTMLEntities(str) {
|
|
22
|
+
return str
|
|
23
|
+
.replace(/&#(\d+);/g, (_, code) => String.fromCharCode(Number(code)))
|
|
24
|
+
.replace(/"/g, '"')
|
|
25
|
+
.replace(/'/g, "'")
|
|
26
|
+
.replace(/</g, '<')
|
|
27
|
+
.replace(/>/g, '>')
|
|
28
|
+
.replace(/&/g, '&');
|
|
29
|
+
}
|
|
15
30
|
/** Registered services */
|
|
16
31
|
let services = new Map();
|
|
17
32
|
const selectorCache = new WeakMap();
|
|
@@ -69,15 +84,32 @@ export function registerDirective(registry, name, fn) {
|
|
|
69
84
|
export function registerService(name, service) {
|
|
70
85
|
services.set(name, service);
|
|
71
86
|
}
|
|
87
|
+
/**
|
|
88
|
+
* Find the nearest scope by walking up the DOM tree.
|
|
89
|
+
* Falls back to rootState if no element scope found.
|
|
90
|
+
*
|
|
91
|
+
* @internal
|
|
92
|
+
*/
|
|
93
|
+
function findServerScope(el, rootState) {
|
|
94
|
+
let current = el;
|
|
95
|
+
while (current) {
|
|
96
|
+
const scope = getElementScope(current);
|
|
97
|
+
if (scope) {
|
|
98
|
+
return scope;
|
|
99
|
+
}
|
|
100
|
+
current = current.parentElement;
|
|
101
|
+
}
|
|
102
|
+
return rootState;
|
|
103
|
+
}
|
|
72
104
|
/**
|
|
73
105
|
* Create resolver config for server-side dependency resolution.
|
|
74
106
|
*
|
|
75
107
|
* @internal
|
|
76
108
|
*/
|
|
77
|
-
function createServerResolverConfig(el, rootState) {
|
|
109
|
+
function createServerResolverConfig(el, scopeState, rootState) {
|
|
78
110
|
return {
|
|
79
111
|
resolveContext: (key) => resolveContext(el, key),
|
|
80
|
-
resolveState: () =>
|
|
112
|
+
resolveState: () => scopeState,
|
|
81
113
|
resolveRootState: () => rootState,
|
|
82
114
|
resolveCustom: (name) => {
|
|
83
115
|
// Look up in ancestor DI providers first (provide option)
|
|
@@ -152,6 +184,27 @@ export async function render(html, state, registry) {
|
|
|
152
184
|
});
|
|
153
185
|
continue;
|
|
154
186
|
}
|
|
187
|
+
// Handle g-scope elements that don't have other directives
|
|
188
|
+
// Add a placeholder entry so they get processed
|
|
189
|
+
if (match.hasAttribute('g-scope')) {
|
|
190
|
+
let hasDirective = false;
|
|
191
|
+
for (const [name] of registry) {
|
|
192
|
+
if (match.hasAttribute(`g-${name}`)) {
|
|
193
|
+
hasDirective = true;
|
|
194
|
+
break;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
if (!hasDirective) {
|
|
198
|
+
index.push({
|
|
199
|
+
el: match,
|
|
200
|
+
name: 'scope',
|
|
201
|
+
directive: null,
|
|
202
|
+
expr: '',
|
|
203
|
+
priority: DirectivePriority.STRUCTURAL,
|
|
204
|
+
isNativeSlot: false
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
}
|
|
155
208
|
for (const [name, directive] of registry) {
|
|
156
209
|
const attr = match.getAttribute(`g-${name}`);
|
|
157
210
|
if (attr !== null) {
|
|
@@ -161,7 +214,7 @@ export async function render(html, state, registry) {
|
|
|
161
214
|
el: match,
|
|
162
215
|
name,
|
|
163
216
|
directive,
|
|
164
|
-
expr: attr,
|
|
217
|
+
expr: decodeHTMLEntities(attr),
|
|
165
218
|
priority: directive.priority ?? DirectivePriority.NORMAL,
|
|
166
219
|
using: registration?.options.using
|
|
167
220
|
});
|
|
@@ -227,7 +280,7 @@ export async function render(html, state, registry) {
|
|
|
227
280
|
// Process g-scope first (inline scope initialization)
|
|
228
281
|
const scopeAttr = el.getAttribute('g-scope');
|
|
229
282
|
if (scopeAttr) {
|
|
230
|
-
const scopeValues = ctx.eval(scopeAttr);
|
|
283
|
+
const scopeValues = ctx.eval(decodeHTMLEntities(scopeAttr));
|
|
231
284
|
if (scopeValues && typeof scopeValues === 'object') {
|
|
232
285
|
Object.assign(state, scopeValues);
|
|
233
286
|
}
|
|
@@ -236,7 +289,7 @@ export async function render(html, state, registry) {
|
|
|
236
289
|
for (const attr of [...el.attributes]) {
|
|
237
290
|
if (attr.name.startsWith('g-bind:')) {
|
|
238
291
|
const targetAttr = attr.name.slice('g-bind:'.length);
|
|
239
|
-
const value = ctx.eval(attr.value);
|
|
292
|
+
const value = ctx.eval(decodeHTMLEntities(attr.value));
|
|
240
293
|
if (value === null || value === undefined) {
|
|
241
294
|
el.removeAttribute(targetAttr);
|
|
242
295
|
}
|
|
@@ -253,14 +306,28 @@ export async function render(html, state, registry) {
|
|
|
253
306
|
if (item.isNativeSlot) {
|
|
254
307
|
processNativeSlot(item.el);
|
|
255
308
|
}
|
|
309
|
+
else if (item.directive === null) {
|
|
310
|
+
// Placeholder for g-scope - already processed above
|
|
311
|
+
continue;
|
|
312
|
+
}
|
|
256
313
|
else {
|
|
257
|
-
|
|
314
|
+
// Determine scope for this directive
|
|
315
|
+
let scopeState;
|
|
316
|
+
if (item.directive.$context?.length) {
|
|
317
|
+
// Directives with $context get their own scope to populate
|
|
318
|
+
const parentScope = findServerScope(item.el, state);
|
|
319
|
+
scopeState = createElementScope(item.el, parentScope);
|
|
320
|
+
}
|
|
321
|
+
else {
|
|
322
|
+
// Other directives use nearest ancestor scope or root
|
|
323
|
+
scopeState = findServerScope(item.el, state);
|
|
324
|
+
}
|
|
325
|
+
const config = createServerResolverConfig(item.el, scopeState, state);
|
|
258
326
|
const args = resolveInjectables(item.directive, item.expr, item.el, ctx.eval.bind(ctx), config, item.using);
|
|
259
327
|
await item.directive(...args);
|
|
260
328
|
// Register as context provider if directive declares $context
|
|
261
329
|
if (item.directive.$context?.length) {
|
|
262
|
-
|
|
263
|
-
registerProvider(item.el, item.directive, localState);
|
|
330
|
+
registerProvider(item.el, item.directive, scopeState);
|
|
264
331
|
}
|
|
265
332
|
}
|
|
266
333
|
}
|
package/dist/types.d.ts
CHANGED
|
@@ -4,7 +4,6 @@
|
|
|
4
4
|
* @packageDocumentation
|
|
5
5
|
*/
|
|
6
6
|
import type { ContextKey } from './context-registry.js';
|
|
7
|
-
import type { Injectable } from './inject.js';
|
|
8
7
|
/**
|
|
9
8
|
* Execution mode for the framework.
|
|
10
9
|
*/
|
|
@@ -160,14 +159,15 @@ export interface DirectiveMeta<T = InjectableRegistry> {
|
|
|
160
159
|
*
|
|
161
160
|
* @example
|
|
162
161
|
* ```ts
|
|
163
|
-
* // String-based injection
|
|
164
162
|
* myDirective.$inject = ['$element', '$scope'];
|
|
163
|
+
* ```
|
|
165
164
|
*
|
|
166
|
-
*
|
|
167
|
-
*
|
|
165
|
+
* For typed context keys, use the `using` option on directive registration:
|
|
166
|
+
* ```ts
|
|
167
|
+
* directive('my-directive', myDirective, { using: [SlotContentContext] });
|
|
168
168
|
* ```
|
|
169
169
|
*/
|
|
170
|
-
$inject?: readonly
|
|
170
|
+
$inject?: readonly string[];
|
|
171
171
|
/**
|
|
172
172
|
* Names this directive exposes as context to descendants.
|
|
173
173
|
*
|