gonia 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +119 -0
- package/dist/client/hydrate.d.ts +54 -0
- package/dist/client/hydrate.js +445 -0
- package/dist/client/index.d.ts +7 -0
- package/dist/client/index.js +6 -0
- package/dist/context.d.ts +40 -0
- package/dist/context.js +69 -0
- package/dist/directives/class.d.ts +21 -0
- package/dist/directives/class.js +42 -0
- package/dist/directives/for.d.ts +29 -0
- package/dist/directives/for.js +265 -0
- package/dist/directives/html.d.ts +16 -0
- package/dist/directives/html.js +19 -0
- package/dist/directives/if.d.ts +25 -0
- package/dist/directives/if.js +133 -0
- package/dist/directives/index.d.ts +15 -0
- package/dist/directives/index.js +15 -0
- package/dist/directives/model.d.ts +27 -0
- package/dist/directives/model.js +134 -0
- package/dist/directives/on.d.ts +21 -0
- package/dist/directives/on.js +54 -0
- package/dist/directives/show.d.ts +15 -0
- package/dist/directives/show.js +19 -0
- package/dist/directives/slot.d.ts +48 -0
- package/dist/directives/slot.js +99 -0
- package/dist/directives/template.d.ts +55 -0
- package/dist/directives/template.js +147 -0
- package/dist/directives/text.d.ts +15 -0
- package/dist/directives/text.js +18 -0
- package/dist/expression.d.ts +60 -0
- package/dist/expression.js +96 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.js +16 -0
- package/dist/inject.d.ts +42 -0
- package/dist/inject.js +63 -0
- package/dist/providers.d.ts +96 -0
- package/dist/providers.js +146 -0
- package/dist/reactivity.d.ts +95 -0
- package/dist/reactivity.js +219 -0
- package/dist/scope.d.ts +43 -0
- package/dist/scope.js +112 -0
- package/dist/server/index.d.ts +7 -0
- package/dist/server/index.js +6 -0
- package/dist/server/render.d.ts +61 -0
- package/dist/server/render.js +243 -0
- package/dist/templates.d.ts +92 -0
- package/dist/templates.js +124 -0
- package/dist/types.d.ts +362 -0
- package/dist/types.js +110 -0
- package/dist/vite/index.d.ts +6 -0
- package/dist/vite/index.js +6 -0
- package/dist/vite/plugin.d.ts +30 -0
- package/dist/vite/plugin.js +127 -0
- package/package.json +67 -0
package/dist/context.js
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Context creation and management.
|
|
3
|
+
*
|
|
4
|
+
* @packageDocumentation
|
|
5
|
+
*/
|
|
6
|
+
import { findRoots } from './expression.js';
|
|
7
|
+
/**
|
|
8
|
+
* Create an evaluation context for directives.
|
|
9
|
+
*
|
|
10
|
+
* @remarks
|
|
11
|
+
* Uses {@link findRoots} to only access state keys that the expression
|
|
12
|
+
* actually references, enabling precise dependency tracking with the
|
|
13
|
+
* reactive system.
|
|
14
|
+
*
|
|
15
|
+
* Supports scoped values via `get()` for things like $component, $renderingChain.
|
|
16
|
+
* Create child contexts with `child()` for nested scopes.
|
|
17
|
+
*
|
|
18
|
+
* @param mode - Execution mode (server or client)
|
|
19
|
+
* @param state - The reactive state object
|
|
20
|
+
* @param scope - Optional scoped values (for context-specific data)
|
|
21
|
+
* @returns A new context
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* ```ts
|
|
25
|
+
* const state = reactive({ user: { name: 'Alice' } });
|
|
26
|
+
* const ctx = createContext(Mode.CLIENT, state);
|
|
27
|
+
* ctx.eval('user.name' as Expression); // 'Alice'
|
|
28
|
+
*
|
|
29
|
+
* const childCtx = ctx.child({ $component: el });
|
|
30
|
+
* childCtx.get('$component'); // el
|
|
31
|
+
* ```
|
|
32
|
+
*/
|
|
33
|
+
export function createContext(mode, state, scope = {}) {
|
|
34
|
+
const ctx = {
|
|
35
|
+
mode,
|
|
36
|
+
eval(expr) {
|
|
37
|
+
const roots = findRoots(expr);
|
|
38
|
+
const fn = new Function(...roots, `return (${expr})`);
|
|
39
|
+
const values = roots.map(r => {
|
|
40
|
+
if (r in scope) {
|
|
41
|
+
return scope[r];
|
|
42
|
+
}
|
|
43
|
+
return state[r];
|
|
44
|
+
});
|
|
45
|
+
return fn(...values);
|
|
46
|
+
},
|
|
47
|
+
get(key) {
|
|
48
|
+
if (key in scope) {
|
|
49
|
+
return scope[key];
|
|
50
|
+
}
|
|
51
|
+
if (key in state) {
|
|
52
|
+
return state[key];
|
|
53
|
+
}
|
|
54
|
+
return undefined;
|
|
55
|
+
},
|
|
56
|
+
child(additions) {
|
|
57
|
+
return createContext(mode, state, { ...scope, ...additions });
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
return ctx;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Create a child context with additional bindings.
|
|
64
|
+
*
|
|
65
|
+
* @deprecated Use ctx.child() instead
|
|
66
|
+
*/
|
|
67
|
+
export function createChildContext(parent, additions) {
|
|
68
|
+
return parent.child(additions);
|
|
69
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dynamic class binding directive.
|
|
3
|
+
*
|
|
4
|
+
* @packageDocumentation
|
|
5
|
+
*/
|
|
6
|
+
import { Directive } from '../types.js';
|
|
7
|
+
/**
|
|
8
|
+
* Bind classes dynamically based on an expression.
|
|
9
|
+
*
|
|
10
|
+
* @remarks
|
|
11
|
+
* Evaluates the expression to get a `{ className: boolean }` object.
|
|
12
|
+
* Classes with truthy values are added, falsy values are removed.
|
|
13
|
+
* Static classes from the HTML are preserved.
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```html
|
|
17
|
+
* <div c-class="{ active: isActive, 'text-red': hasError }">
|
|
18
|
+
* <div c-class="{ [dynamicClass]: true }">
|
|
19
|
+
* ```
|
|
20
|
+
*/
|
|
21
|
+
export declare const cclass: Directive<['$expr', '$element', '$eval']>;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dynamic class binding directive.
|
|
3
|
+
*
|
|
4
|
+
* @packageDocumentation
|
|
5
|
+
*/
|
|
6
|
+
import { directive } from '../types.js';
|
|
7
|
+
import { effect } from '../reactivity.js';
|
|
8
|
+
/**
|
|
9
|
+
* Bind classes dynamically based on an expression.
|
|
10
|
+
*
|
|
11
|
+
* @remarks
|
|
12
|
+
* Evaluates the expression to get a `{ className: boolean }` object.
|
|
13
|
+
* Classes with truthy values are added, falsy values are removed.
|
|
14
|
+
* Static classes from the HTML are preserved.
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```html
|
|
18
|
+
* <div c-class="{ active: isActive, 'text-red': hasError }">
|
|
19
|
+
* <div c-class="{ [dynamicClass]: true }">
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
export const cclass = function cclass($expr, $element, $eval) {
|
|
23
|
+
effect(() => {
|
|
24
|
+
const classObj = $eval($expr);
|
|
25
|
+
if (classObj === null || classObj === undefined) {
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
if (typeof classObj !== 'object') {
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
for (const [className, shouldAdd] of Object.entries(classObj)) {
|
|
32
|
+
if (shouldAdd) {
|
|
33
|
+
$element.classList.add(className);
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
$element.classList.remove(className);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
};
|
|
41
|
+
cclass.$inject = ['$expr', '$element', '$eval'];
|
|
42
|
+
directive('c-class', cclass);
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Loop/iteration directive.
|
|
3
|
+
*
|
|
4
|
+
* @packageDocumentation
|
|
5
|
+
*/
|
|
6
|
+
import { Directive } from '../types.js';
|
|
7
|
+
/** Attribute used to mark elements processed by c-for */
|
|
8
|
+
export declare const FOR_PROCESSED_ATTR = "data-c-for-processed";
|
|
9
|
+
/** Attribute used to mark template content that should be skipped during SSR */
|
|
10
|
+
export declare const FOR_TEMPLATE_ATTR = "data-c-for-template";
|
|
11
|
+
/**
|
|
12
|
+
* Iterate over array or object items.
|
|
13
|
+
*
|
|
14
|
+
* @remarks
|
|
15
|
+
* Creates a copy of the template element for each item in the iterable.
|
|
16
|
+
* Supports arrays and objects. For arrays, provides item and index.
|
|
17
|
+
* For objects, provides value and key.
|
|
18
|
+
*
|
|
19
|
+
* On server: wraps template in <template> element, renders items after it.
|
|
20
|
+
* On client: finds <template c-for>, extracts template, sets up reactive loop.
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* ```html
|
|
24
|
+
* <li c-for="item in items" c-text="item.name"></li>
|
|
25
|
+
* <li c-for="(item, index) in items" c-text="index + ': ' + item.name"></li>
|
|
26
|
+
* <div c-for="(value, key) in object" c-text="key + ': ' + value"></div>
|
|
27
|
+
* ```
|
|
28
|
+
*/
|
|
29
|
+
export declare const cfor: Directive<['$expr', '$element', '$eval', '$state', '$mode']>;
|
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Loop/iteration directive.
|
|
3
|
+
*
|
|
4
|
+
* @packageDocumentation
|
|
5
|
+
*/
|
|
6
|
+
import { directive, DirectivePriority, Mode } from '../types.js';
|
|
7
|
+
import { effect, createEffectScope, createScope } from '../reactivity.js';
|
|
8
|
+
import { createContext } from '../context.js';
|
|
9
|
+
/**
|
|
10
|
+
* Parse a c-for expression.
|
|
11
|
+
*
|
|
12
|
+
* Supports:
|
|
13
|
+
* - `item in items`
|
|
14
|
+
* - `(item, index) in items`
|
|
15
|
+
* - `(value, key) in object`
|
|
16
|
+
*/
|
|
17
|
+
function parseForExpression(expr) {
|
|
18
|
+
const trimmed = expr.trim();
|
|
19
|
+
// Match "(item, index) in iterable" or "item in iterable"
|
|
20
|
+
const match = trimmed.match(/^\(?([a-zA-Z_$][a-zA-Z0-9_$]*)(?:\s*,\s*([a-zA-Z_$][a-zA-Z0-9_$]*))?\)?\s+in\s+(.+)$/);
|
|
21
|
+
if (!match) {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
return {
|
|
25
|
+
itemName: match[1],
|
|
26
|
+
indexName: match[2] || null,
|
|
27
|
+
iterableName: match[3].trim()
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
/** Attribute used to mark elements processed by c-for */
|
|
31
|
+
export const FOR_PROCESSED_ATTR = 'data-c-for-processed';
|
|
32
|
+
/** Attribute used to mark template content that should be skipped during SSR */
|
|
33
|
+
export const FOR_TEMPLATE_ATTR = 'data-c-for-template';
|
|
34
|
+
/**
|
|
35
|
+
* Process directives on a cloned element within a child scope.
|
|
36
|
+
*/
|
|
37
|
+
function processClonedElement(el, parentState, scopeAdditions, mode) {
|
|
38
|
+
// Mark this element as processed by c-for so hydrate skips it
|
|
39
|
+
el.setAttribute(FOR_PROCESSED_ATTR, '');
|
|
40
|
+
const childScope = createScope(parentState, scopeAdditions);
|
|
41
|
+
const childCtx = createContext(mode, childScope);
|
|
42
|
+
// Process c-text directives
|
|
43
|
+
const textAttr = el.getAttribute('c-text');
|
|
44
|
+
if (textAttr) {
|
|
45
|
+
const value = childCtx.eval(textAttr);
|
|
46
|
+
el.textContent = String(value ?? '');
|
|
47
|
+
}
|
|
48
|
+
// Process c-class directives
|
|
49
|
+
const classAttr = el.getAttribute('c-class');
|
|
50
|
+
if (classAttr) {
|
|
51
|
+
const classObj = childCtx.eval(classAttr);
|
|
52
|
+
if (classObj && typeof classObj === 'object') {
|
|
53
|
+
for (const [className, shouldAdd] of Object.entries(classObj)) {
|
|
54
|
+
if (shouldAdd) {
|
|
55
|
+
el.classList.add(className);
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
el.classList.remove(className);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
// Process c-show directives
|
|
64
|
+
const showAttr = el.getAttribute('c-show');
|
|
65
|
+
if (showAttr) {
|
|
66
|
+
const value = childCtx.eval(showAttr);
|
|
67
|
+
el.style.display = value ? '' : 'none';
|
|
68
|
+
}
|
|
69
|
+
// Process c-on directives (format: "event: handler") - client only
|
|
70
|
+
if (mode === Mode.CLIENT) {
|
|
71
|
+
const onAttr = el.getAttribute('c-on');
|
|
72
|
+
if (onAttr) {
|
|
73
|
+
setupEventHandler(el, onAttr, childCtx, childScope);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
// Process children recursively
|
|
77
|
+
for (const child of el.children) {
|
|
78
|
+
processClonedElement(child, childScope, {}, mode);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Set up an event handler on an element.
|
|
83
|
+
* Expression format: "event: handler"
|
|
84
|
+
*/
|
|
85
|
+
function setupEventHandler(el, expr, ctx, state) {
|
|
86
|
+
const colonIdx = expr.indexOf(':');
|
|
87
|
+
if (colonIdx === -1) {
|
|
88
|
+
console.error(`Invalid c-on expression: ${expr}. Expected "event: handler"`);
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
const eventName = expr.slice(0, colonIdx).trim();
|
|
92
|
+
const handlerExpr = expr.slice(colonIdx + 1).trim();
|
|
93
|
+
const handler = (event) => {
|
|
94
|
+
const result = ctx.eval(handlerExpr);
|
|
95
|
+
if (typeof result === 'function') {
|
|
96
|
+
result.call(state, event);
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
el.addEventListener(eventName, handler);
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Render loop items (used by both server and client).
|
|
103
|
+
*/
|
|
104
|
+
function renderItems(template, parent, insertAfterNode, parsed, $eval, $state, mode) {
|
|
105
|
+
const { itemName, indexName, iterableName } = parsed;
|
|
106
|
+
const iterable = $eval(iterableName);
|
|
107
|
+
const renderedElements = [];
|
|
108
|
+
if (iterable == null) {
|
|
109
|
+
return renderedElements;
|
|
110
|
+
}
|
|
111
|
+
let items;
|
|
112
|
+
if (Array.isArray(iterable)) {
|
|
113
|
+
items = iterable.map((item, index) => [item, index]);
|
|
114
|
+
}
|
|
115
|
+
else if (typeof iterable === 'object') {
|
|
116
|
+
items = Object.entries(iterable).map(([k, v]) => [v, k]);
|
|
117
|
+
}
|
|
118
|
+
else {
|
|
119
|
+
return renderedElements;
|
|
120
|
+
}
|
|
121
|
+
let insertAfter = insertAfterNode;
|
|
122
|
+
const length = items.length;
|
|
123
|
+
for (let i = 0; i < length; i++) {
|
|
124
|
+
const [value, key] = items[i];
|
|
125
|
+
const clone = template.cloneNode(true);
|
|
126
|
+
// Remove template marker from clone (it's not a template, it's a rendered item)
|
|
127
|
+
clone.removeAttribute(FOR_TEMPLATE_ATTR);
|
|
128
|
+
const scopeAdditions = {
|
|
129
|
+
[itemName]: value,
|
|
130
|
+
$index: i,
|
|
131
|
+
$first: i === 0,
|
|
132
|
+
$last: i === length - 1,
|
|
133
|
+
$even: i % 2 === 0,
|
|
134
|
+
$odd: i % 2 === 1
|
|
135
|
+
};
|
|
136
|
+
if (indexName) {
|
|
137
|
+
scopeAdditions[indexName] = key;
|
|
138
|
+
}
|
|
139
|
+
processClonedElement(clone, $state, scopeAdditions, mode);
|
|
140
|
+
if (insertAfter.nextSibling) {
|
|
141
|
+
parent.insertBefore(clone, insertAfter.nextSibling);
|
|
142
|
+
}
|
|
143
|
+
else {
|
|
144
|
+
parent.appendChild(clone);
|
|
145
|
+
}
|
|
146
|
+
renderedElements.push(clone);
|
|
147
|
+
insertAfter = clone;
|
|
148
|
+
}
|
|
149
|
+
return renderedElements;
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Remove SSR-rendered items (siblings with FOR_PROCESSED_ATTR after template).
|
|
153
|
+
*/
|
|
154
|
+
function removeSSRItems(templateEl) {
|
|
155
|
+
let sibling = templateEl.nextElementSibling;
|
|
156
|
+
while (sibling && sibling.hasAttribute(FOR_PROCESSED_ATTR)) {
|
|
157
|
+
const next = sibling.nextElementSibling;
|
|
158
|
+
sibling.remove();
|
|
159
|
+
sibling = next;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Iterate over array or object items.
|
|
164
|
+
*
|
|
165
|
+
* @remarks
|
|
166
|
+
* Creates a copy of the template element for each item in the iterable.
|
|
167
|
+
* Supports arrays and objects. For arrays, provides item and index.
|
|
168
|
+
* For objects, provides value and key.
|
|
169
|
+
*
|
|
170
|
+
* On server: wraps template in <template> element, renders items after it.
|
|
171
|
+
* On client: finds <template c-for>, extracts template, sets up reactive loop.
|
|
172
|
+
*
|
|
173
|
+
* @example
|
|
174
|
+
* ```html
|
|
175
|
+
* <li c-for="item in items" c-text="item.name"></li>
|
|
176
|
+
* <li c-for="(item, index) in items" c-text="index + ': ' + item.name"></li>
|
|
177
|
+
* <div c-for="(value, key) in object" c-text="key + ': ' + value"></div>
|
|
178
|
+
* ```
|
|
179
|
+
*/
|
|
180
|
+
export const cfor = function cfor($expr, $element, $eval, $state, $mode) {
|
|
181
|
+
const parsed = parseForExpression($expr);
|
|
182
|
+
if (!parsed) {
|
|
183
|
+
console.error(`Invalid c-for expression: ${$expr}`);
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
const parent = $element.parentNode;
|
|
187
|
+
if (!parent) {
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
const isTemplateElement = $element.tagName === 'TEMPLATE';
|
|
191
|
+
// Server-side rendering
|
|
192
|
+
if ($mode === Mode.SERVER) {
|
|
193
|
+
// Create a <template> element to hold the original
|
|
194
|
+
const templateWrapper = $element.ownerDocument.createElement('template');
|
|
195
|
+
templateWrapper.setAttribute('c-for', $expr);
|
|
196
|
+
// Clone the original element (without c-for attr) into the template
|
|
197
|
+
const templateContent = $element.cloneNode(true);
|
|
198
|
+
templateContent.removeAttribute('c-for');
|
|
199
|
+
// Mark as template content so render loop skips it
|
|
200
|
+
templateContent.setAttribute(FOR_TEMPLATE_ATTR, '');
|
|
201
|
+
// Append directly to template element (linkedom doesn't support .content)
|
|
202
|
+
// Browsers will automatically move this to .content when parsing
|
|
203
|
+
templateWrapper.appendChild(templateContent);
|
|
204
|
+
// Replace original with template wrapper
|
|
205
|
+
parent.replaceChild(templateWrapper, $element);
|
|
206
|
+
// Render items after the template
|
|
207
|
+
renderItems(templateContent, parent, templateWrapper, parsed, $eval, $state, $mode);
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
// Client-side: check if hydrating from SSR or fresh render
|
|
211
|
+
if (isTemplateElement) {
|
|
212
|
+
// Hydrating from SSR: element is <template c-for="...">
|
|
213
|
+
const templateWrapper = $element;
|
|
214
|
+
const templateContent = templateWrapper.content.firstElementChild;
|
|
215
|
+
if (!templateContent) {
|
|
216
|
+
console.error('c-for template element has no content');
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
// Remove SSR-rendered items
|
|
220
|
+
removeSSRItems(templateWrapper);
|
|
221
|
+
// Set up reactive loop
|
|
222
|
+
let renderedElements = [];
|
|
223
|
+
let scope = null;
|
|
224
|
+
effect(() => {
|
|
225
|
+
if (scope) {
|
|
226
|
+
scope.stop();
|
|
227
|
+
}
|
|
228
|
+
for (const el of renderedElements) {
|
|
229
|
+
el.remove();
|
|
230
|
+
}
|
|
231
|
+
scope = createEffectScope();
|
|
232
|
+
scope.run(() => {
|
|
233
|
+
renderedElements = renderItems(templateContent, parent, templateWrapper, parsed, $eval, $state, Mode.CLIENT);
|
|
234
|
+
});
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
else {
|
|
238
|
+
// Fresh client render: element has c-for attribute directly
|
|
239
|
+
// Wrap in template element for consistency
|
|
240
|
+
const templateWrapper = $element.ownerDocument.createElement('template');
|
|
241
|
+
templateWrapper.setAttribute('c-for', $expr);
|
|
242
|
+
const templateContent = $element.cloneNode(true);
|
|
243
|
+
templateContent.removeAttribute('c-for');
|
|
244
|
+
templateWrapper.content.appendChild(templateContent);
|
|
245
|
+
parent.replaceChild(templateWrapper, $element);
|
|
246
|
+
// Set up reactive loop
|
|
247
|
+
let renderedElements = [];
|
|
248
|
+
let scope = null;
|
|
249
|
+
effect(() => {
|
|
250
|
+
if (scope) {
|
|
251
|
+
scope.stop();
|
|
252
|
+
}
|
|
253
|
+
for (const el of renderedElements) {
|
|
254
|
+
el.remove();
|
|
255
|
+
}
|
|
256
|
+
scope = createEffectScope();
|
|
257
|
+
scope.run(() => {
|
|
258
|
+
renderedElements = renderItems(templateContent, parent, templateWrapper, parsed, $eval, $state, Mode.CLIENT);
|
|
259
|
+
});
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
};
|
|
263
|
+
cfor.$inject = ['$expr', '$element', '$eval', '$state', '$mode'];
|
|
264
|
+
cfor.priority = DirectivePriority.STRUCTURAL;
|
|
265
|
+
directive('c-for', cfor);
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { Directive } from '../types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Set element's innerHTML from an expression.
|
|
4
|
+
*
|
|
5
|
+
* @remarks
|
|
6
|
+
* Evaluates the expression and sets the element's `innerHTML`.
|
|
7
|
+
*
|
|
8
|
+
* @warning Only use with trusted content. User input can lead to XSS attacks.
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```html
|
|
12
|
+
* <div c-html="formattedContent"></div>
|
|
13
|
+
* <div c-html="'<strong>' + title + '</strong>'"></div>
|
|
14
|
+
* ```
|
|
15
|
+
*/
|
|
16
|
+
export declare const html: Directive<['$expr', '$element', '$eval']>;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { directive } from '../types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Set element's innerHTML from an expression.
|
|
4
|
+
*
|
|
5
|
+
* @remarks
|
|
6
|
+
* Evaluates the expression and sets the element's `innerHTML`.
|
|
7
|
+
*
|
|
8
|
+
* @warning Only use with trusted content. User input can lead to XSS attacks.
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```html
|
|
12
|
+
* <div c-html="formattedContent"></div>
|
|
13
|
+
* <div c-html="'<strong>' + title + '</strong>'"></div>
|
|
14
|
+
* ```
|
|
15
|
+
*/
|
|
16
|
+
export const html = function html($expr, $element, $eval) {
|
|
17
|
+
$element.innerHTML = String($eval($expr) ?? '');
|
|
18
|
+
};
|
|
19
|
+
directive('c-html', html);
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Conditional rendering directive.
|
|
3
|
+
*
|
|
4
|
+
* @packageDocumentation
|
|
5
|
+
*/
|
|
6
|
+
import { Directive } from '../types.js';
|
|
7
|
+
/** Attribute used to mark elements processed by c-if */
|
|
8
|
+
export declare const IF_PROCESSED_ATTR = "data-c-if-processed";
|
|
9
|
+
/**
|
|
10
|
+
* Conditionally render an element.
|
|
11
|
+
*
|
|
12
|
+
* @remarks
|
|
13
|
+
* Unlike c-show which uses display:none, c-if completely removes
|
|
14
|
+
* the element from the DOM when the condition is falsy.
|
|
15
|
+
*
|
|
16
|
+
* On server: evaluates once and removes element if false.
|
|
17
|
+
* On client: sets up reactive effect to toggle element.
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* ```html
|
|
21
|
+
* <div c-if="isLoggedIn">Welcome back!</div>
|
|
22
|
+
* <div c-if="items.length > 0">Has items</div>
|
|
23
|
+
* ```
|
|
24
|
+
*/
|
|
25
|
+
export declare const cif: Directive<['$expr', '$element', '$eval', '$state', '$mode']>;
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Conditional rendering directive.
|
|
3
|
+
*
|
|
4
|
+
* @packageDocumentation
|
|
5
|
+
*/
|
|
6
|
+
import { directive, DirectivePriority, Mode } from '../types.js';
|
|
7
|
+
import { effect } from '../reactivity.js';
|
|
8
|
+
import { createContext } from '../context.js';
|
|
9
|
+
import { createScope } from '../reactivity.js';
|
|
10
|
+
/** Attribute used to mark elements processed by c-if */
|
|
11
|
+
export const IF_PROCESSED_ATTR = 'data-c-if-processed';
|
|
12
|
+
/**
|
|
13
|
+
* Process directives on a conditionally rendered element.
|
|
14
|
+
*/
|
|
15
|
+
function processConditionalElement(el, parentState, mode) {
|
|
16
|
+
el.setAttribute(IF_PROCESSED_ATTR, '');
|
|
17
|
+
const childScope = createScope(parentState, {});
|
|
18
|
+
const childCtx = createContext(mode, childScope);
|
|
19
|
+
// Process c-text directives
|
|
20
|
+
const textAttr = el.getAttribute('c-text');
|
|
21
|
+
if (textAttr) {
|
|
22
|
+
const value = childCtx.eval(textAttr);
|
|
23
|
+
el.textContent = String(value ?? '');
|
|
24
|
+
}
|
|
25
|
+
// Process c-class directives
|
|
26
|
+
const classAttr = el.getAttribute('c-class');
|
|
27
|
+
if (classAttr) {
|
|
28
|
+
const classObj = childCtx.eval(classAttr);
|
|
29
|
+
if (classObj && typeof classObj === 'object') {
|
|
30
|
+
for (const [className, shouldAdd] of Object.entries(classObj)) {
|
|
31
|
+
if (shouldAdd) {
|
|
32
|
+
el.classList.add(className);
|
|
33
|
+
}
|
|
34
|
+
else {
|
|
35
|
+
el.classList.remove(className);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
// Process c-show directives
|
|
41
|
+
const showAttr = el.getAttribute('c-show');
|
|
42
|
+
if (showAttr) {
|
|
43
|
+
const value = childCtx.eval(showAttr);
|
|
44
|
+
el.style.display = value ? '' : 'none';
|
|
45
|
+
}
|
|
46
|
+
// Process c-on directives (format: "event: handler") - client only
|
|
47
|
+
if (mode === Mode.CLIENT) {
|
|
48
|
+
const onAttr = el.getAttribute('c-on');
|
|
49
|
+
if (onAttr) {
|
|
50
|
+
const colonIdx = onAttr.indexOf(':');
|
|
51
|
+
if (colonIdx !== -1) {
|
|
52
|
+
const eventName = onAttr.slice(0, colonIdx).trim();
|
|
53
|
+
const handlerExpr = onAttr.slice(colonIdx + 1).trim();
|
|
54
|
+
el.addEventListener(eventName, (event) => {
|
|
55
|
+
const result = childCtx.eval(handlerExpr);
|
|
56
|
+
if (typeof result === 'function') {
|
|
57
|
+
result.call(childScope, event);
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
// Process children recursively
|
|
64
|
+
for (const child of el.children) {
|
|
65
|
+
processConditionalElement(child, childScope, mode);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Conditionally render an element.
|
|
70
|
+
*
|
|
71
|
+
* @remarks
|
|
72
|
+
* Unlike c-show which uses display:none, c-if completely removes
|
|
73
|
+
* the element from the DOM when the condition is falsy.
|
|
74
|
+
*
|
|
75
|
+
* On server: evaluates once and removes element if false.
|
|
76
|
+
* On client: sets up reactive effect to toggle element.
|
|
77
|
+
*
|
|
78
|
+
* @example
|
|
79
|
+
* ```html
|
|
80
|
+
* <div c-if="isLoggedIn">Welcome back!</div>
|
|
81
|
+
* <div c-if="items.length > 0">Has items</div>
|
|
82
|
+
* ```
|
|
83
|
+
*/
|
|
84
|
+
export const cif = function cif($expr, $element, $eval, $state, $mode) {
|
|
85
|
+
const parent = $element.parentNode;
|
|
86
|
+
if (!parent) {
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
// Server-side: evaluate once and remove if false
|
|
90
|
+
if ($mode === Mode.SERVER) {
|
|
91
|
+
const condition = $eval($expr);
|
|
92
|
+
if (!condition) {
|
|
93
|
+
$element.remove();
|
|
94
|
+
}
|
|
95
|
+
else {
|
|
96
|
+
// Process child directives
|
|
97
|
+
$element.removeAttribute('c-if');
|
|
98
|
+
processConditionalElement($element, $state, $mode);
|
|
99
|
+
}
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
// Client-side: set up reactive effect
|
|
103
|
+
const placeholder = $element.ownerDocument.createComment(` c-if: ${$expr} `);
|
|
104
|
+
parent.insertBefore(placeholder, $element);
|
|
105
|
+
const template = $element.cloneNode(true);
|
|
106
|
+
template.removeAttribute('c-if');
|
|
107
|
+
$element.remove();
|
|
108
|
+
let renderedElement = null;
|
|
109
|
+
effect(() => {
|
|
110
|
+
const condition = $eval($expr);
|
|
111
|
+
if (condition) {
|
|
112
|
+
if (!renderedElement) {
|
|
113
|
+
renderedElement = template.cloneNode(true);
|
|
114
|
+
processConditionalElement(renderedElement, $state, Mode.CLIENT);
|
|
115
|
+
if (placeholder.nextSibling) {
|
|
116
|
+
parent.insertBefore(renderedElement, placeholder.nextSibling);
|
|
117
|
+
}
|
|
118
|
+
else {
|
|
119
|
+
parent.appendChild(renderedElement);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
else {
|
|
124
|
+
if (renderedElement) {
|
|
125
|
+
renderedElement.remove();
|
|
126
|
+
renderedElement = null;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
};
|
|
131
|
+
cif.$inject = ['$expr', '$element', '$eval', '$state', '$mode'];
|
|
132
|
+
cif.priority = DirectivePriority.STRUCTURAL;
|
|
133
|
+
directive('c-if', cif);
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Built-in directives.
|
|
3
|
+
*
|
|
4
|
+
* @packageDocumentation
|
|
5
|
+
*/
|
|
6
|
+
export { text } from './text.js';
|
|
7
|
+
export { html } from './html.js';
|
|
8
|
+
export { show } from './show.js';
|
|
9
|
+
export { template } from './template.js';
|
|
10
|
+
export { slot, processNativeSlot } from './slot.js';
|
|
11
|
+
export { cclass } from './class.js';
|
|
12
|
+
export { model } from './model.js';
|
|
13
|
+
export { on } from './on.js';
|
|
14
|
+
export { cfor } from './for.js';
|
|
15
|
+
export { cif } from './if.js';
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Built-in directives.
|
|
3
|
+
*
|
|
4
|
+
* @packageDocumentation
|
|
5
|
+
*/
|
|
6
|
+
export { text } from './text.js';
|
|
7
|
+
export { html } from './html.js';
|
|
8
|
+
export { show } from './show.js';
|
|
9
|
+
export { template } from './template.js';
|
|
10
|
+
export { slot, processNativeSlot } from './slot.js';
|
|
11
|
+
export { cclass } from './class.js';
|
|
12
|
+
export { model } from './model.js';
|
|
13
|
+
export { on } from './on.js';
|
|
14
|
+
export { cfor } from './for.js';
|
|
15
|
+
export { cif } from './if.js';
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Two-way binding directive.
|
|
3
|
+
*
|
|
4
|
+
* @packageDocumentation
|
|
5
|
+
*/
|
|
6
|
+
import { Directive } from '../types.js';
|
|
7
|
+
/**
|
|
8
|
+
* Bind form element value to state with two-way data binding.
|
|
9
|
+
*
|
|
10
|
+
* @remarks
|
|
11
|
+
* Updates the element value when state changes, and updates state
|
|
12
|
+
* when the user modifies the element. Handles different input types:
|
|
13
|
+
* - text/textarea: binds to value, uses input event
|
|
14
|
+
* - checkbox: binds to checked, uses change event
|
|
15
|
+
* - radio: binds to checked, uses change event
|
|
16
|
+
* - select: binds to value, uses change event
|
|
17
|
+
* - number: binds to value (as number), uses input event
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* ```html
|
|
21
|
+
* <input c-model="name">
|
|
22
|
+
* <input type="checkbox" c-model="isActive">
|
|
23
|
+
* <select c-model="selectedOption">
|
|
24
|
+
* <textarea c-model="description"></textarea>
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
export declare const model: Directive<['$expr', '$element', '$eval', '$rootState']>;
|