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/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 string[];
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 = ['http', 'userService'];
38
- * getInjectables(myDirective); // ['http', 'userService']
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): string[];
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 = ['http', 'userService'];
32
- * getInjectables(myDirective); // ['http', 'userService']
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
- let current = el.parentElement;
87
- while (current) {
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
- current = current.parentElement;
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
- let current = el.parentElement;
111
- while (current) {
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
- current = current.parentElement;
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 { getInjectables } from './inject.js';
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
- let current = includeSelf ? el : el.parentElement;
77
- while (current) {
78
- const scope = elementScopes.get(current);
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 and call directive
118
- const inject = getInjectables(fn);
119
- const args = inject.map((dep) => {
120
- switch (dep) {
121
- case '$element':
122
- return this;
123
- case '$state':
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) {
@@ -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
- * Resolve dependencies for a directive based on its $inject array.
58
+ * Create resolver config for server-side dependency resolution.
57
59
  *
58
60
  * @internal
59
61
  */
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
- });
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 args = resolveDependencies(item.directive, item.expr, item.el, ctx, state);
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
- * Maps a tuple of injectable names to their corresponding types.
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 name strings
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 custom type map
73
- * type Args2 = MapInjectables<['$element', 'custom'], { $element: Element; custom: MyType }>;
74
- * // => [Element, MyType]
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 string[];
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 & keyof (InjectableRegistry & T))[] = readonly (string & keyof InjectableRegistry)[], T extends Record<string, unknown> = {}> = ((...args: MapInjectables<K, InjectableRegistry & T>) => void | Promise<void>) & DirectiveMeta<InjectableRegistry & T>;
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
+ }
@@ -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.