gonia 0.1.2 → 0.1.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/client/hydrate.js +40 -58
- package/dist/context-registry.d.ts +130 -0
- package/dist/context-registry.js +153 -0
- package/dist/directives/slot.d.ts +4 -2
- package/dist/directives/slot.js +11 -16
- package/dist/directives/template.d.ts +9 -2
- package/dist/directives/template.js +14 -13
- package/dist/dom.d.ts +29 -0
- package/dist/dom.js +39 -0
- package/dist/index.d.ts +6 -2
- package/dist/index.js +4 -2
- package/dist/inject.d.ts +47 -6
- package/dist/inject.js +66 -4
- package/dist/providers.js +9 -12
- package/dist/scope.js +13 -22
- package/dist/server/render.js +29 -40
- package/dist/types.d.ts +88 -11
- package/dist/types.js +41 -0
- package/dist/vite/plugin.d.ts +27 -0
- package/dist/vite/plugin.js +56 -3
- package/package.json +5 -1
package/dist/index.d.ts
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
*
|
|
8
8
|
* @packageDocumentation
|
|
9
9
|
*/
|
|
10
|
-
export { Mode, Expression, Context, Directive, directive, getDirective, getDirectiveNames, clearDirectives } from './types.js';
|
|
10
|
+
export { Mode, Expression, Context, Directive, directive, getDirective, getDirectiveNames, clearDirectives, configureDirective } from './types.js';
|
|
11
11
|
export type { DirectiveMeta } from './types.js';
|
|
12
12
|
export { createContext, createChildContext } from './context.js';
|
|
13
13
|
export { reactive, effect, createScope, createEffectScope } from './reactivity.js';
|
|
@@ -15,6 +15,10 @@ export type { EffectScope } from './reactivity.js';
|
|
|
15
15
|
export { createTemplateRegistry, createMemoryRegistry, createServerRegistry } from './templates.js';
|
|
16
16
|
export type { TemplateRegistry } from './templates.js';
|
|
17
17
|
export { findRoots, parseInterpolation } from './expression.js';
|
|
18
|
-
export { getInjectables } from './inject.js';
|
|
18
|
+
export { getInjectables, isContextKey } from './inject.js';
|
|
19
|
+
export type { Injectable } from './inject.js';
|
|
19
20
|
export { getRootScope, clearRootScope } from './scope.js';
|
|
21
|
+
export { findAncestor } from './dom.js';
|
|
22
|
+
export { createContextKey, registerContext, resolveContext, hasContext, removeContext, clearContexts } from './context-registry.js';
|
|
23
|
+
export type { ContextKey } from './context-registry.js';
|
|
20
24
|
export * as directives from './directives/index.js';
|
package/dist/index.js
CHANGED
|
@@ -7,11 +7,13 @@
|
|
|
7
7
|
*
|
|
8
8
|
* @packageDocumentation
|
|
9
9
|
*/
|
|
10
|
-
export { Mode, directive, getDirective, getDirectiveNames, clearDirectives } from './types.js';
|
|
10
|
+
export { Mode, directive, getDirective, getDirectiveNames, clearDirectives, configureDirective } from './types.js';
|
|
11
11
|
export { createContext, createChildContext } from './context.js';
|
|
12
12
|
export { reactive, effect, createScope, createEffectScope } from './reactivity.js';
|
|
13
13
|
export { createTemplateRegistry, createMemoryRegistry, createServerRegistry } from './templates.js';
|
|
14
14
|
export { findRoots, parseInterpolation } from './expression.js';
|
|
15
|
-
export { getInjectables } from './inject.js';
|
|
15
|
+
export { getInjectables, isContextKey } from './inject.js';
|
|
16
16
|
export { getRootScope, clearRootScope } from './scope.js';
|
|
17
|
+
export { findAncestor } from './dom.js';
|
|
18
|
+
export { createContextKey, registerContext, resolveContext, hasContext, removeContext, clearContexts } from './context-registry.js';
|
|
17
19
|
export * as directives from './directives/index.js';
|
package/dist/inject.d.ts
CHANGED
|
@@ -11,11 +11,21 @@
|
|
|
11
11
|
*
|
|
12
12
|
* @packageDocumentation
|
|
13
13
|
*/
|
|
14
|
+
import type { ContextKey } from './context-registry.js';
|
|
15
|
+
import type { Expression, EvalFn } from './types.js';
|
|
16
|
+
/**
|
|
17
|
+
* An injectable dependency - either a string name or a typed context key.
|
|
18
|
+
*/
|
|
19
|
+
export type Injectable = string | ContextKey<unknown>;
|
|
20
|
+
/**
|
|
21
|
+
* Check if a value is a ContextKey.
|
|
22
|
+
*/
|
|
23
|
+
export declare function isContextKey(value: unknown): value is ContextKey<unknown>;
|
|
14
24
|
/**
|
|
15
25
|
* A function with optional `$inject` annotation.
|
|
16
26
|
*/
|
|
17
27
|
interface InjectableFunction extends Function {
|
|
18
|
-
$inject?: readonly
|
|
28
|
+
$inject?: readonly Injectable[];
|
|
19
29
|
}
|
|
20
30
|
/**
|
|
21
31
|
* Get the list of injectable dependencies for a function.
|
|
@@ -25,7 +35,7 @@ interface InjectableFunction extends Function {
|
|
|
25
35
|
* In production, always use `$inject` to survive minification.
|
|
26
36
|
*
|
|
27
37
|
* @param fn - The function to inspect
|
|
28
|
-
* @returns Array of dependency names
|
|
38
|
+
* @returns Array of dependency names or context keys
|
|
29
39
|
*
|
|
30
40
|
* @example
|
|
31
41
|
* ```ts
|
|
@@ -33,10 +43,41 @@ interface InjectableFunction extends Function {
|
|
|
33
43
|
* const myDirective = (expr, ctx, el, http, userService) => {};
|
|
34
44
|
* getInjectables(myDirective); // ['expr', 'ctx', 'el', 'http', 'userService']
|
|
35
45
|
*
|
|
36
|
-
* // Production - explicit annotation
|
|
37
|
-
* myDirective.$inject = ['
|
|
38
|
-
* getInjectables(myDirective); // ['
|
|
46
|
+
* // Production - explicit annotation with context keys
|
|
47
|
+
* myDirective.$inject = ['$element', SlotContentContext];
|
|
48
|
+
* getInjectables(myDirective); // ['$element', SlotContentContext]
|
|
39
49
|
* ```
|
|
40
50
|
*/
|
|
41
|
-
export declare function getInjectables(fn: InjectableFunction):
|
|
51
|
+
export declare function getInjectables(fn: InjectableFunction): Injectable[];
|
|
52
|
+
/**
|
|
53
|
+
* Configuration for dependency resolution.
|
|
54
|
+
*/
|
|
55
|
+
export interface DependencyResolverConfig {
|
|
56
|
+
/** Resolve a ContextKey to its value */
|
|
57
|
+
resolveContext: (key: ContextKey<unknown>) => unknown;
|
|
58
|
+
/** Resolve $state injectable */
|
|
59
|
+
resolveState: () => Record<string, unknown>;
|
|
60
|
+
/** Resolve $rootState injectable (may be same as state) */
|
|
61
|
+
resolveRootState?: () => Record<string, unknown>;
|
|
62
|
+
/** Resolve custom injectable by name (services, providers) */
|
|
63
|
+
resolveCustom?: (name: string) => unknown | undefined;
|
|
64
|
+
/** Current mode */
|
|
65
|
+
mode: 'server' | 'client';
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Resolve dependencies for a directive function.
|
|
69
|
+
*
|
|
70
|
+
* @remarks
|
|
71
|
+
* Unified dependency resolution used by both client and server.
|
|
72
|
+
* Handles $inject arrays, ContextKey injection, and the `using` option.
|
|
73
|
+
*
|
|
74
|
+
* @param fn - The directive function (with optional $inject)
|
|
75
|
+
* @param expr - The expression string from the directive attribute
|
|
76
|
+
* @param element - The target DOM element
|
|
77
|
+
* @param evalFn - Function to evaluate expressions
|
|
78
|
+
* @param config - Resolution configuration
|
|
79
|
+
* @param using - Optional array of context keys to append
|
|
80
|
+
* @returns Array of resolved dependency values
|
|
81
|
+
*/
|
|
82
|
+
export declare function resolveDependencies(fn: InjectableFunction, expr: Expression | string, element: Element, evalFn: EvalFn, config: DependencyResolverConfig, using?: ContextKey<unknown>[]): unknown[];
|
|
42
83
|
export {};
|
package/dist/inject.js
CHANGED
|
@@ -11,6 +11,12 @@
|
|
|
11
11
|
*
|
|
12
12
|
* @packageDocumentation
|
|
13
13
|
*/
|
|
14
|
+
/**
|
|
15
|
+
* Check if a value is a ContextKey.
|
|
16
|
+
*/
|
|
17
|
+
export function isContextKey(value) {
|
|
18
|
+
return typeof value === 'object' && value !== null && 'id' in value && typeof value.id === 'symbol';
|
|
19
|
+
}
|
|
14
20
|
/**
|
|
15
21
|
* Get the list of injectable dependencies for a function.
|
|
16
22
|
*
|
|
@@ -19,7 +25,7 @@
|
|
|
19
25
|
* In production, always use `$inject` to survive minification.
|
|
20
26
|
*
|
|
21
27
|
* @param fn - The function to inspect
|
|
22
|
-
* @returns Array of dependency names
|
|
28
|
+
* @returns Array of dependency names or context keys
|
|
23
29
|
*
|
|
24
30
|
* @example
|
|
25
31
|
* ```ts
|
|
@@ -27,9 +33,9 @@
|
|
|
27
33
|
* const myDirective = (expr, ctx, el, http, userService) => {};
|
|
28
34
|
* getInjectables(myDirective); // ['expr', 'ctx', 'el', 'http', 'userService']
|
|
29
35
|
*
|
|
30
|
-
* // Production - explicit annotation
|
|
31
|
-
* myDirective.$inject = ['
|
|
32
|
-
* getInjectables(myDirective); // ['
|
|
36
|
+
* // Production - explicit annotation with context keys
|
|
37
|
+
* myDirective.$inject = ['$element', SlotContentContext];
|
|
38
|
+
* getInjectables(myDirective); // ['$element', SlotContentContext]
|
|
33
39
|
* ```
|
|
34
40
|
*/
|
|
35
41
|
export function getInjectables(fn) {
|
|
@@ -61,3 +67,59 @@ function parseFunctionParams(fn) {
|
|
|
61
67
|
.map(p => p.replace(/\s*=.*$/, ''))
|
|
62
68
|
.filter(Boolean);
|
|
63
69
|
}
|
|
70
|
+
/**
|
|
71
|
+
* Resolve dependencies for a directive function.
|
|
72
|
+
*
|
|
73
|
+
* @remarks
|
|
74
|
+
* Unified dependency resolution used by both client and server.
|
|
75
|
+
* Handles $inject arrays, ContextKey injection, and the `using` option.
|
|
76
|
+
*
|
|
77
|
+
* @param fn - The directive function (with optional $inject)
|
|
78
|
+
* @param expr - The expression string from the directive attribute
|
|
79
|
+
* @param element - The target DOM element
|
|
80
|
+
* @param evalFn - Function to evaluate expressions
|
|
81
|
+
* @param config - Resolution configuration
|
|
82
|
+
* @param using - Optional array of context keys to append
|
|
83
|
+
* @returns Array of resolved dependency values
|
|
84
|
+
*/
|
|
85
|
+
export function resolveDependencies(fn, expr, element, evalFn, config, using) {
|
|
86
|
+
const inject = getInjectables(fn);
|
|
87
|
+
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
|
+
switch (dep) {
|
|
94
|
+
case '$expr':
|
|
95
|
+
return expr;
|
|
96
|
+
case '$element':
|
|
97
|
+
return element;
|
|
98
|
+
case '$eval':
|
|
99
|
+
return evalFn;
|
|
100
|
+
case '$state':
|
|
101
|
+
return config.resolveState();
|
|
102
|
+
case '$rootState':
|
|
103
|
+
return config.resolveRootState?.() ?? config.resolveState();
|
|
104
|
+
case '$mode':
|
|
105
|
+
return config.mode;
|
|
106
|
+
default: {
|
|
107
|
+
// Look up in custom resolver (services, providers, etc.)
|
|
108
|
+
if (config.resolveCustom) {
|
|
109
|
+
const resolved = config.resolveCustom(dep);
|
|
110
|
+
if (resolved !== undefined) {
|
|
111
|
+
return resolved;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
throw new Error(`Unknown injectable: ${dep}`);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
// Append contexts from `using` option
|
|
119
|
+
if (using?.length) {
|
|
120
|
+
for (const key of using) {
|
|
121
|
+
args.push(config.resolveContext(key));
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
return args;
|
|
125
|
+
}
|
package/dist/providers.js
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* @packageDocumentation
|
|
5
5
|
*/
|
|
6
6
|
import { reactive } from './reactivity.js';
|
|
7
|
+
import { findAncestor } from './dom.js';
|
|
7
8
|
/**
|
|
8
9
|
* Local state stored per element.
|
|
9
10
|
*
|
|
@@ -83,15 +84,13 @@ export function registerDIProviders(el, provideMap) {
|
|
|
83
84
|
* @internal
|
|
84
85
|
*/
|
|
85
86
|
export function resolveFromDIProviders(el, name) {
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
const provideMap = diProviders.get(current);
|
|
87
|
+
return findAncestor(el, (e) => {
|
|
88
|
+
const provideMap = diProviders.get(e);
|
|
89
89
|
if (provideMap && name in provideMap) {
|
|
90
90
|
return provideMap[name];
|
|
91
91
|
}
|
|
92
|
-
|
|
93
|
-
}
|
|
94
|
-
return undefined;
|
|
92
|
+
return undefined;
|
|
93
|
+
});
|
|
95
94
|
}
|
|
96
95
|
/**
|
|
97
96
|
* Resolve a context value from ancestor elements.
|
|
@@ -107,15 +106,13 @@ export function resolveFromDIProviders(el, name) {
|
|
|
107
106
|
* @internal
|
|
108
107
|
*/
|
|
109
108
|
export function resolveFromProviders(el, name) {
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
const info = contextProviders.get(current);
|
|
109
|
+
return findAncestor(el, (e) => {
|
|
110
|
+
const info = contextProviders.get(e);
|
|
113
111
|
if (info?.directive.$context?.includes(name)) {
|
|
114
112
|
return info.state;
|
|
115
113
|
}
|
|
116
|
-
|
|
117
|
-
}
|
|
118
|
-
return undefined;
|
|
114
|
+
return undefined;
|
|
115
|
+
});
|
|
119
116
|
}
|
|
120
117
|
/**
|
|
121
118
|
* Clear local state for an element.
|
package/dist/scope.js
CHANGED
|
@@ -6,7 +6,9 @@
|
|
|
6
6
|
import { reactive } from './reactivity.js';
|
|
7
7
|
import { createContext } from './context.js';
|
|
8
8
|
import { Mode } from './types.js';
|
|
9
|
-
import {
|
|
9
|
+
import { resolveDependencies } from './inject.js';
|
|
10
|
+
import { findAncestor } from './dom.js';
|
|
11
|
+
import { resolveContext } from './context-registry.js';
|
|
10
12
|
/** WeakMap to store element scopes */
|
|
11
13
|
const elementScopes = new WeakMap();
|
|
12
14
|
/** Root scope for top-level directives without explicit parent scope */
|
|
@@ -73,13 +75,9 @@ export function getElementScope(el) {
|
|
|
73
75
|
* @returns The nearest scope, or root scope if none found and fallback enabled
|
|
74
76
|
*/
|
|
75
77
|
export function findParentScope(el, includeSelf = false, useRootFallback = true) {
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
if (scope) {
|
|
80
|
-
return scope;
|
|
81
|
-
}
|
|
82
|
-
current = current.parentElement;
|
|
78
|
+
const scope = findAncestor(el, (e) => elementScopes.get(e), includeSelf);
|
|
79
|
+
if (scope) {
|
|
80
|
+
return scope;
|
|
83
81
|
}
|
|
84
82
|
// Fall back to root scope for top-level directives
|
|
85
83
|
return useRootFallback ? getRootScope() : undefined;
|
|
@@ -114,20 +112,13 @@ fn, options) {
|
|
|
114
112
|
const scope = createElementScope(this, parentScope);
|
|
115
113
|
// Create context for expression evaluation
|
|
116
114
|
const ctx = createContext(Mode.CLIENT, scope);
|
|
117
|
-
// Resolve dependencies
|
|
118
|
-
const
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
return scope;
|
|
125
|
-
case '$eval':
|
|
126
|
-
return ctx.eval.bind(ctx);
|
|
127
|
-
default:
|
|
128
|
-
return undefined;
|
|
129
|
-
}
|
|
130
|
-
});
|
|
115
|
+
// Resolve dependencies using shared resolver
|
|
116
|
+
const config = {
|
|
117
|
+
resolveContext: (key) => resolveContext(this, key),
|
|
118
|
+
resolveState: () => scope,
|
|
119
|
+
mode: 'client'
|
|
120
|
+
};
|
|
121
|
+
const args = resolveDependencies(fn, '', this, ctx.eval.bind(ctx), config, options.using);
|
|
131
122
|
const result = fn(...args);
|
|
132
123
|
// Handle async directives
|
|
133
124
|
if (result instanceof Promise) {
|
package/dist/server/render.js
CHANGED
|
@@ -4,12 +4,14 @@
|
|
|
4
4
|
* @packageDocumentation
|
|
5
5
|
*/
|
|
6
6
|
import { parseHTML } from 'linkedom';
|
|
7
|
-
import { Mode, DirectivePriority } from '../types.js';
|
|
7
|
+
import { Mode, DirectivePriority, getDirective } from '../types.js';
|
|
8
8
|
import { createContext } from '../context.js';
|
|
9
9
|
import { processNativeSlot } from '../directives/slot.js';
|
|
10
10
|
import { getLocalState, registerProvider, resolveFromProviders, resolveFromDIProviders } from '../providers.js';
|
|
11
11
|
import { FOR_PROCESSED_ATTR, FOR_TEMPLATE_ATTR } from '../directives/for.js';
|
|
12
12
|
import { IF_PROCESSED_ATTR } from '../directives/if.js';
|
|
13
|
+
import { resolveDependencies as resolveInjectables } from '../inject.js';
|
|
14
|
+
import { resolveContext } from '../context-registry.js';
|
|
13
15
|
/** Registered services */
|
|
14
16
|
let services = new Map();
|
|
15
17
|
const selectorCache = new WeakMap();
|
|
@@ -53,46 +55,29 @@ export function registerService(name, service) {
|
|
|
53
55
|
services.set(name, service);
|
|
54
56
|
}
|
|
55
57
|
/**
|
|
56
|
-
*
|
|
58
|
+
* Create resolver config for server-side dependency resolution.
|
|
57
59
|
*
|
|
58
60
|
* @internal
|
|
59
61
|
*/
|
|
60
|
-
function
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
return
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
return
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
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
|
-
});
|
|
62
|
+
function createServerResolverConfig(el, rootState) {
|
|
63
|
+
return {
|
|
64
|
+
resolveContext: (key) => resolveContext(el, key),
|
|
65
|
+
resolveState: () => getLocalState(el) ?? rootState,
|
|
66
|
+
resolveRootState: () => rootState,
|
|
67
|
+
resolveCustom: (name) => {
|
|
68
|
+
// Look up in ancestor DI providers first (provide option)
|
|
69
|
+
const diProvided = resolveFromDIProviders(el, name);
|
|
70
|
+
if (diProvided !== undefined)
|
|
71
|
+
return diProvided;
|
|
72
|
+
// Look up in global services registry
|
|
73
|
+
const service = services.get(name);
|
|
74
|
+
if (service !== undefined)
|
|
75
|
+
return service;
|
|
76
|
+
// Look up in ancestor context providers ($context)
|
|
77
|
+
return resolveFromProviders(el, name);
|
|
78
|
+
},
|
|
79
|
+
mode: 'server'
|
|
80
|
+
};
|
|
96
81
|
}
|
|
97
82
|
/**
|
|
98
83
|
* Render HTML with directives on the server.
|
|
@@ -150,12 +135,15 @@ export async function render(html, state, registry) {
|
|
|
150
135
|
for (const [name, directive] of registry) {
|
|
151
136
|
const attr = match.getAttribute(`g-${name}`);
|
|
152
137
|
if (attr !== null) {
|
|
138
|
+
// Look up options from the global directive registry
|
|
139
|
+
const registration = getDirective(`g-${name}`);
|
|
153
140
|
index.push({
|
|
154
141
|
el: match,
|
|
155
142
|
name,
|
|
156
143
|
directive,
|
|
157
144
|
expr: attr,
|
|
158
|
-
priority: directive.priority ?? DirectivePriority.NORMAL
|
|
145
|
+
priority: directive.priority ?? DirectivePriority.NORMAL,
|
|
146
|
+
using: registration?.options.using
|
|
159
147
|
});
|
|
160
148
|
}
|
|
161
149
|
}
|
|
@@ -225,7 +213,8 @@ export async function render(html, state, registry) {
|
|
|
225
213
|
processNativeSlot(item.el);
|
|
226
214
|
}
|
|
227
215
|
else {
|
|
228
|
-
const
|
|
216
|
+
const config = createServerResolverConfig(item.el, state);
|
|
217
|
+
const args = resolveInjectables(item.directive, item.expr, item.el, ctx.eval.bind(ctx), config, item.using);
|
|
229
218
|
await item.directive(...args);
|
|
230
219
|
// Register as context provider if directive declares $context
|
|
231
220
|
if (item.directive.$context?.length) {
|
package/dist/types.d.ts
CHANGED
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
*
|
|
4
4
|
* @packageDocumentation
|
|
5
5
|
*/
|
|
6
|
+
import type { ContextKey } from './context-registry.js';
|
|
7
|
+
import type { Injectable } from './inject.js';
|
|
6
8
|
/**
|
|
7
9
|
* Execution mode for the framework.
|
|
8
10
|
*/
|
|
@@ -59,9 +61,13 @@ export interface InjectableRegistry {
|
|
|
59
61
|
$mode: Mode;
|
|
60
62
|
}
|
|
61
63
|
/**
|
|
62
|
-
*
|
|
64
|
+
* Extract the value type from a ContextKey.
|
|
65
|
+
*/
|
|
66
|
+
type ContextKeyValue<K> = K extends ContextKey<infer V> ? V : never;
|
|
67
|
+
/**
|
|
68
|
+
* Maps a tuple of injectable names or context keys to their corresponding types.
|
|
63
69
|
*
|
|
64
|
-
* @typeParam K - Tuple of injectable
|
|
70
|
+
* @typeParam K - Tuple of injectable names (strings) or ContextKey objects
|
|
65
71
|
* @typeParam T - Type map (defaults to InjectableRegistry)
|
|
66
72
|
*
|
|
67
73
|
* @example
|
|
@@ -69,13 +75,14 @@ export interface InjectableRegistry {
|
|
|
69
75
|
* type Args = MapInjectables<['$element', '$state']>;
|
|
70
76
|
* // => [Element, Record<string, unknown>]
|
|
71
77
|
*
|
|
72
|
-
* // With
|
|
73
|
-
*
|
|
74
|
-
*
|
|
78
|
+
* // With context keys
|
|
79
|
+
* const MyContext = createContextKey<{ value: number }>('MyContext');
|
|
80
|
+
* type Args2 = MapInjectables<['$element', typeof MyContext]>;
|
|
81
|
+
* // => [Element, { value: number }]
|
|
75
82
|
* ```
|
|
76
83
|
*/
|
|
77
|
-
export type MapInjectables<K extends readonly string[], T = InjectableRegistry> = {
|
|
78
|
-
[I in keyof K]: K[I] extends keyof T ? T[K[I]] : unknown;
|
|
84
|
+
export type MapInjectables<K extends readonly (string | ContextKey<unknown>)[], T = InjectableRegistry> = {
|
|
85
|
+
[I in keyof K]: K[I] extends ContextKey<unknown> ? ContextKeyValue<K[I]> : K[I] extends keyof T ? T[K[I]] : unknown;
|
|
79
86
|
};
|
|
80
87
|
/**
|
|
81
88
|
* Evaluation context passed to directives.
|
|
@@ -148,9 +155,19 @@ export interface DirectiveMeta<T = InjectableRegistry> {
|
|
|
148
155
|
* - `$eval`: Function to evaluate expressions: `(expr) => value`
|
|
149
156
|
* - `$state`: Local reactive state object (isolated per element)
|
|
150
157
|
* - Any registered service names
|
|
158
|
+
* - Any `ContextKey` for typed context resolution
|
|
151
159
|
* - Any names provided by ancestor directives via `$context`
|
|
160
|
+
*
|
|
161
|
+
* @example
|
|
162
|
+
* ```ts
|
|
163
|
+
* // String-based injection
|
|
164
|
+
* myDirective.$inject = ['$element', '$state'];
|
|
165
|
+
*
|
|
166
|
+
* // With typed context keys
|
|
167
|
+
* myDirective.$inject = ['$element', SlotContentContext];
|
|
168
|
+
* ```
|
|
152
169
|
*/
|
|
153
|
-
$inject?: readonly
|
|
170
|
+
$inject?: readonly Injectable[];
|
|
154
171
|
/**
|
|
155
172
|
* Names this directive exposes as context to descendants.
|
|
156
173
|
*
|
|
@@ -187,9 +204,9 @@ export interface DirectiveMeta<T = InjectableRegistry> {
|
|
|
187
204
|
*
|
|
188
205
|
* @remarks
|
|
189
206
|
* Use this type annotation to get contextual typing for directive parameters.
|
|
190
|
-
* The tuple of keys maps to parameter types from InjectableRegistry.
|
|
207
|
+
* The tuple of keys maps to parameter types from InjectableRegistry or ContextKey types.
|
|
191
208
|
*
|
|
192
|
-
* @typeParam K - Tuple of injectable key names
|
|
209
|
+
* @typeParam K - Tuple of injectable key names or ContextKey objects
|
|
193
210
|
* @typeParam T - Optional custom type map to extend InjectableRegistry
|
|
194
211
|
*
|
|
195
212
|
* @example
|
|
@@ -204,6 +221,12 @@ export interface DirectiveMeta<T = InjectableRegistry> {
|
|
|
204
221
|
* $element.textContent = String($eval($expr) ?? '');
|
|
205
222
|
* };
|
|
206
223
|
*
|
|
224
|
+
* // With typed context keys
|
|
225
|
+
* const slot: Directive<['$element', typeof SlotContentContext]> = ($element, content) => {
|
|
226
|
+
* // content is typed as SlotContent
|
|
227
|
+
* console.log(content.slots);
|
|
228
|
+
* };
|
|
229
|
+
*
|
|
207
230
|
* // With custom types (extend InjectableRegistry first)
|
|
208
231
|
* declare module 'gonia' {
|
|
209
232
|
* interface InjectableRegistry {
|
|
@@ -215,7 +238,7 @@ export interface DirectiveMeta<T = InjectableRegistry> {
|
|
|
215
238
|
* };
|
|
216
239
|
* ```
|
|
217
240
|
*/
|
|
218
|
-
export type Directive<K extends readonly (string
|
|
241
|
+
export type Directive<K extends readonly (string | ContextKey<unknown>)[] = readonly (string & keyof InjectableRegistry)[], T extends Record<string, unknown> = {}> = ((...args: MapInjectables<K, InjectableRegistry & T>) => void | Promise<void>) & DirectiveMeta<InjectableRegistry & T>;
|
|
219
242
|
/**
|
|
220
243
|
* Attributes passed to template functions.
|
|
221
244
|
*/
|
|
@@ -297,6 +320,34 @@ export interface DirectiveOptions {
|
|
|
297
320
|
* ```
|
|
298
321
|
*/
|
|
299
322
|
provide?: Record<string, unknown>;
|
|
323
|
+
/**
|
|
324
|
+
* Context keys this directive uses.
|
|
325
|
+
*
|
|
326
|
+
* @remarks
|
|
327
|
+
* Declares which contexts the directive depends on. The resolved
|
|
328
|
+
* context values are appended to the directive function parameters
|
|
329
|
+
* in the order they appear in this array.
|
|
330
|
+
*
|
|
331
|
+
* This enables:
|
|
332
|
+
* - Static analysis of context dependencies
|
|
333
|
+
* - Automatic `$inject` generation by the Vite plugin
|
|
334
|
+
* - Type-safe context access without manual `resolveContext` calls
|
|
335
|
+
*
|
|
336
|
+
* @example
|
|
337
|
+
* ```ts
|
|
338
|
+
* const ThemeContext = createContextKey<{ mode: 'light' | 'dark' }>('Theme');
|
|
339
|
+
* const UserContext = createContextKey<{ name: string }>('User');
|
|
340
|
+
*
|
|
341
|
+
* directive('themed-greeting', ($element, $state, theme, user) => {
|
|
342
|
+
* // theme and user are resolved from the using array
|
|
343
|
+
* $element.textContent = `Hello ${user.name}!`;
|
|
344
|
+
* $element.className = theme.mode;
|
|
345
|
+
* }, {
|
|
346
|
+
* using: [ThemeContext, UserContext]
|
|
347
|
+
* });
|
|
348
|
+
* ```
|
|
349
|
+
*/
|
|
350
|
+
using?: ContextKey<unknown>[];
|
|
300
351
|
}
|
|
301
352
|
/** Registered directive with options */
|
|
302
353
|
export interface DirectiveRegistration {
|
|
@@ -359,4 +410,30 @@ export declare function getDirectiveNames(): string[];
|
|
|
359
410
|
* Primarily useful for testing.
|
|
360
411
|
*/
|
|
361
412
|
export declare function clearDirectives(): void;
|
|
413
|
+
/**
|
|
414
|
+
* Configure options for an existing directive.
|
|
415
|
+
*
|
|
416
|
+
* @remarks
|
|
417
|
+
* Merges the provided options with any existing options for the directive.
|
|
418
|
+
* If the directive hasn't been registered yet, stores the options to be
|
|
419
|
+
* applied when it is registered.
|
|
420
|
+
*
|
|
421
|
+
* This is useful for configuring built-in or third-party directives
|
|
422
|
+
* without needing access to the directive function.
|
|
423
|
+
*
|
|
424
|
+
* @param name - The directive name
|
|
425
|
+
* @param options - Options to merge
|
|
426
|
+
*
|
|
427
|
+
* @example
|
|
428
|
+
* ```ts
|
|
429
|
+
* // Add scope to a built-in directive
|
|
430
|
+
* configureDirective('g-text', { scope: true });
|
|
431
|
+
*
|
|
432
|
+
* // Add template to a custom element
|
|
433
|
+
* configureDirective('app-header', {
|
|
434
|
+
* template: '<header><slot></slot></header>'
|
|
435
|
+
* });
|
|
436
|
+
* ```
|
|
437
|
+
*/
|
|
438
|
+
export declare function configureDirective(name: string, options: Partial<DirectiveOptions>): void;
|
|
362
439
|
export {};
|
package/dist/types.js
CHANGED
|
@@ -108,3 +108,44 @@ export function getDirectiveNames() {
|
|
|
108
108
|
export function clearDirectives() {
|
|
109
109
|
directiveRegistry.clear();
|
|
110
110
|
}
|
|
111
|
+
/**
|
|
112
|
+
* Configure options for an existing directive.
|
|
113
|
+
*
|
|
114
|
+
* @remarks
|
|
115
|
+
* Merges the provided options with any existing options for the directive.
|
|
116
|
+
* If the directive hasn't been registered yet, stores the options to be
|
|
117
|
+
* applied when it is registered.
|
|
118
|
+
*
|
|
119
|
+
* This is useful for configuring built-in or third-party directives
|
|
120
|
+
* without needing access to the directive function.
|
|
121
|
+
*
|
|
122
|
+
* @param name - The directive name
|
|
123
|
+
* @param options - Options to merge
|
|
124
|
+
*
|
|
125
|
+
* @example
|
|
126
|
+
* ```ts
|
|
127
|
+
* // Add scope to a built-in directive
|
|
128
|
+
* configureDirective('g-text', { scope: true });
|
|
129
|
+
*
|
|
130
|
+
* // Add template to a custom element
|
|
131
|
+
* configureDirective('app-header', {
|
|
132
|
+
* template: '<header><slot></slot></header>'
|
|
133
|
+
* });
|
|
134
|
+
* ```
|
|
135
|
+
*/
|
|
136
|
+
export function configureDirective(name, options) {
|
|
137
|
+
const existing = directiveRegistry.get(name);
|
|
138
|
+
if (existing) {
|
|
139
|
+
directiveRegistry.set(name, {
|
|
140
|
+
fn: existing.fn,
|
|
141
|
+
options: { ...existing.options, ...options }
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
else {
|
|
145
|
+
// Store options for later - directive will be registered soon
|
|
146
|
+
directiveRegistry.set(name, {
|
|
147
|
+
fn: null,
|
|
148
|
+
options: options
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
}
|
package/dist/vite/plugin.d.ts
CHANGED
|
@@ -10,6 +10,14 @@
|
|
|
10
10
|
* @packageDocumentation
|
|
11
11
|
*/
|
|
12
12
|
import type { Plugin } from 'vite';
|
|
13
|
+
/**
|
|
14
|
+
* Options for directive registration.
|
|
15
|
+
*/
|
|
16
|
+
export interface DirectiveOptionsConfig {
|
|
17
|
+
scope?: boolean;
|
|
18
|
+
template?: string | ((attrs: Record<string, string>) => string | Promise<string>);
|
|
19
|
+
provide?: Record<string, unknown>;
|
|
20
|
+
}
|
|
13
21
|
/**
|
|
14
22
|
* Plugin options.
|
|
15
23
|
*/
|
|
@@ -48,6 +56,25 @@ export interface GoniaPluginOptions {
|
|
|
48
56
|
* @example ['app-', 'my-', 'ui-']
|
|
49
57
|
*/
|
|
50
58
|
directiveElementPrefixes?: string[];
|
|
59
|
+
/**
|
|
60
|
+
* Options to apply to directives.
|
|
61
|
+
*
|
|
62
|
+
* Can be an object mapping directive names to options, or a function
|
|
63
|
+
* that receives the directive name and returns options.
|
|
64
|
+
*
|
|
65
|
+
* @example
|
|
66
|
+
* ```ts
|
|
67
|
+
* // Object form - explicit per-directive
|
|
68
|
+
* directiveOptions: {
|
|
69
|
+
* 'g-text': { scope: true },
|
|
70
|
+
* 'app-card': { template: '<div class="card"><slot></slot></div>' }
|
|
71
|
+
* }
|
|
72
|
+
*
|
|
73
|
+
* // Function form - dynamic/pattern-based
|
|
74
|
+
* directiveOptions: (name) => name.startsWith('app-') ? { scope: true } : undefined
|
|
75
|
+
* ```
|
|
76
|
+
*/
|
|
77
|
+
directiveOptions?: Record<string, DirectiveOptionsConfig> | ((name: string) => DirectiveOptionsConfig | undefined);
|
|
51
78
|
}
|
|
52
79
|
/**
|
|
53
80
|
* Gonia Vite plugin.
|