@dinoreic/fez 0.4.1 → 0.5.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +707 -209
- package/bin/fez +16 -6
- package/bin/fez-compile +347 -0
- package/bin/fez-debug +25 -0
- package/bin/fez-index +16 -4
- package/bin/refactor +699 -0
- package/dist/fez.js +142 -33
- package/dist/fez.js.map +4 -4
- package/fez.d.ts +533 -0
- package/package.json +25 -15
- package/src/fez/compile.js +396 -164
- package/src/fez/connect.js +249 -146
- package/src/fez/defaults.js +272 -92
- package/src/fez/instance.js +673 -514
- package/src/fez/lib/await-helper.js +64 -0
- package/src/fez/lib/global-state.js +22 -4
- package/src/fez/lib/index.js +140 -0
- package/src/fez/lib/localstorage.js +44 -0
- package/src/fez/lib/n.js +38 -23
- package/src/fez/lib/pubsub.js +208 -0
- package/src/fez/lib/svelte-template-lib.js +339 -0
- package/src/fez/lib/svelte-template.js +472 -0
- package/src/fez/lib/template.js +114 -119
- package/src/fez/morph.js +384 -0
- package/src/fez/root.js +279 -209
- package/src/fez/utility.js +319 -149
- package/src/fez/utils/dump.js +114 -84
- package/src/fez/utils/highlight_all.js +1 -1
- package/src/fez.js +65 -43
- package/src/svelte-cde-adapter.coffee +10 -2
- package/src/fez/vendor/idiomorph.js +0 -860
package/src/fez/connect.js
CHANGED
|
@@ -1,215 +1,318 @@
|
|
|
1
|
-
import createTemplate from './lib/template.js'
|
|
2
|
-
import FezBase from './instance.js'
|
|
3
|
-
|
|
4
1
|
/**
|
|
5
|
-
*
|
|
6
|
-
*
|
|
2
|
+
* Fez Component Registration & Connection
|
|
3
|
+
*
|
|
4
|
+
* This file handles:
|
|
5
|
+
* - Registering components with customElements
|
|
6
|
+
* - Transforming plain classes to FezBase subclasses
|
|
7
|
+
* - Instantiating components when they appear in DOM
|
|
8
|
+
*
|
|
9
|
+
* Flow:
|
|
10
|
+
* 1. connect(name, class) - registers custom element
|
|
11
|
+
* 2. connectedCallback() - when element appears in DOM
|
|
12
|
+
* 3. connectNode() - creates instance, renders, calls lifecycle
|
|
7
13
|
*/
|
|
8
|
-
const observer = new MutationObserver((mutationsList, _) => {
|
|
9
|
-
for (const mutation of mutationsList) {
|
|
10
|
-
if (mutation.type === 'attributes') {
|
|
11
|
-
const fez = mutation.target.fez
|
|
12
|
-
const name = mutation.attributeName
|
|
13
|
-
const value = mutation.target.getAttribute(name)
|
|
14
14
|
|
|
15
|
+
import createTemplate from "./lib/template.js";
|
|
16
|
+
import FezBase from "./instance.js";
|
|
17
|
+
|
|
18
|
+
// =============================================================================
|
|
19
|
+
// CONSTANTS
|
|
20
|
+
// =============================================================================
|
|
21
|
+
|
|
22
|
+
const SELF_CLOSING_TAGS = new Set([
|
|
23
|
+
"area",
|
|
24
|
+
"base",
|
|
25
|
+
"br",
|
|
26
|
+
"col",
|
|
27
|
+
"embed",
|
|
28
|
+
"hr",
|
|
29
|
+
"img",
|
|
30
|
+
"input",
|
|
31
|
+
"link",
|
|
32
|
+
"meta",
|
|
33
|
+
"source",
|
|
34
|
+
"track",
|
|
35
|
+
"wbr",
|
|
36
|
+
]);
|
|
37
|
+
|
|
38
|
+
// Attribute observer for reactive props
|
|
39
|
+
const attrObserver = new MutationObserver((mutations) => {
|
|
40
|
+
for (const mutation of mutations) {
|
|
41
|
+
if (mutation.type === "attributes") {
|
|
42
|
+
const fez = mutation.target.fez;
|
|
15
43
|
if (fez) {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
44
|
+
const name = mutation.attributeName;
|
|
45
|
+
const value = mutation.target.getAttribute(name);
|
|
46
|
+
fez.props[name] = value;
|
|
47
|
+
fez.onPropsChange(name, value);
|
|
19
48
|
}
|
|
20
49
|
}
|
|
21
50
|
}
|
|
22
51
|
});
|
|
23
52
|
|
|
53
|
+
// =============================================================================
|
|
54
|
+
// MAIN CONNECT FUNCTION
|
|
55
|
+
// =============================================================================
|
|
56
|
+
|
|
24
57
|
/**
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
* @param {
|
|
58
|
+
* Register a Fez component
|
|
59
|
+
*
|
|
60
|
+
* @param {string} name - Custom element name (must contain dash)
|
|
61
|
+
* @param {Class} klass - Component class
|
|
62
|
+
*
|
|
28
63
|
* @example
|
|
29
|
-
* Fez('
|
|
30
|
-
* HTML = '<
|
|
31
|
-
* CSS = '
|
|
64
|
+
* Fez('ui-button', class {
|
|
65
|
+
* HTML = '<button><slot /></button>'
|
|
66
|
+
* CSS = 'button { color: blue; }'
|
|
67
|
+
* init() { console.log('created') }
|
|
32
68
|
* })
|
|
33
69
|
*/
|
|
34
70
|
export default function connect(name, klass) {
|
|
35
71
|
const Fez = globalThis.window?.Fez || globalThis.Fez;
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
72
|
+
|
|
73
|
+
// Validate name
|
|
74
|
+
if (!name.includes("-")) {
|
|
75
|
+
console.error(`Fez: Invalid name "${name}". Must contain a dash.`);
|
|
76
|
+
return;
|
|
39
77
|
}
|
|
40
78
|
|
|
41
|
-
// Transform
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
79
|
+
// Transform to FezBase subclass
|
|
80
|
+
klass = ensureFezBase(Fez, name, klass);
|
|
81
|
+
|
|
82
|
+
// Process HTML template
|
|
83
|
+
if (klass.html) {
|
|
84
|
+
klass.html = klass.html
|
|
85
|
+
.replace(
|
|
86
|
+
/<slot(\s[^>]*)?>/,
|
|
87
|
+
`<div class="fez-slot" fez-keep="default-slot"$1>`,
|
|
88
|
+
)
|
|
89
|
+
.replace("</slot>", `</div>`);
|
|
45
90
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
.concat(Object.getOwnPropertyNames(klass.prototype))
|
|
49
|
-
.filter(el => !['constructor', 'prototype'].includes(el))
|
|
91
|
+
klass.fezHtmlFunc = createTemplate(klass.html, { name });
|
|
92
|
+
}
|
|
50
93
|
|
|
51
|
-
|
|
94
|
+
// Register CSS
|
|
95
|
+
if (klass.css) {
|
|
96
|
+
klass.css = Fez.globalCss(klass.css, { name });
|
|
97
|
+
}
|
|
52
98
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
if (klassObj.GLOBAL) { newKlass.GLOBAL = klassObj.GLOBAL } // Global instance reference
|
|
56
|
-
if (klassObj.CSS) { newKlass.css = klassObj.CSS } // Component styles
|
|
57
|
-
if (klassObj.HTML) {
|
|
58
|
-
newKlass.html = closeCustomTags(klassObj.HTML) // Component template
|
|
59
|
-
}
|
|
60
|
-
if (klassObj.NAME) { newKlass.nodeName = klassObj.NAME } // Custom DOM node name
|
|
99
|
+
// Store class in index
|
|
100
|
+
Fez.index.ensure(name).class = klass;
|
|
61
101
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
102
|
+
// Register custom element
|
|
103
|
+
if (!customElements.get(name)) {
|
|
104
|
+
customElements.define(
|
|
105
|
+
name,
|
|
106
|
+
class extends HTMLElement {
|
|
107
|
+
connectedCallback() {
|
|
108
|
+
if (shouldRenderFast(this, klass)) {
|
|
109
|
+
connectNode(name, this);
|
|
110
|
+
} else {
|
|
111
|
+
requestAnimationFrame(() => connectNode(name, this));
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
},
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
66
118
|
|
|
67
|
-
|
|
119
|
+
// =============================================================================
|
|
120
|
+
// CLASS TRANSFORMATION
|
|
121
|
+
// =============================================================================
|
|
68
122
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
123
|
+
/**
|
|
124
|
+
* Transform plain class to FezBase subclass
|
|
125
|
+
* Maps uppercase config props (HTML, CSS, etc.)
|
|
126
|
+
*/
|
|
127
|
+
function ensureFezBase(Fez, name, klass) {
|
|
128
|
+
// Already a FezBase subclass
|
|
129
|
+
if (klass.prototype instanceof FezBase) {
|
|
130
|
+
if (klass.html) klass.html = closeCustomTags(klass.html);
|
|
131
|
+
return klass;
|
|
73
132
|
}
|
|
74
133
|
|
|
75
|
-
//
|
|
76
|
-
|
|
77
|
-
|
|
134
|
+
// Create FezBase subclass
|
|
135
|
+
const instance = new klass();
|
|
136
|
+
const newKlass = class extends FezBase {};
|
|
78
137
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
138
|
+
// Copy properties and methods
|
|
139
|
+
const props = [
|
|
140
|
+
...Object.getOwnPropertyNames(instance),
|
|
141
|
+
...Object.getOwnPropertyNames(klass.prototype),
|
|
142
|
+
].filter((p) => p !== "constructor" && p !== "prototype");
|
|
82
143
|
|
|
83
|
-
|
|
84
|
-
|
|
144
|
+
for (const prop of props) {
|
|
145
|
+
newKlass.prototype[prop] = instance[prop];
|
|
85
146
|
}
|
|
86
147
|
|
|
87
|
-
//
|
|
88
|
-
|
|
89
|
-
|
|
148
|
+
// Map config properties
|
|
149
|
+
const configMap = {
|
|
150
|
+
FAST: "FAST",
|
|
151
|
+
GLOBAL: "GLOBAL",
|
|
152
|
+
NAME: "nodeName",
|
|
153
|
+
};
|
|
154
|
+
for (const [from, to] of Object.entries(configMap)) {
|
|
155
|
+
if (instance[from]) newKlass[to] = instance[from];
|
|
90
156
|
}
|
|
91
157
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
connectedCallback() {
|
|
97
|
-
// Fez.onReady(()=>{connectNode(name, this)})
|
|
98
|
-
// connectNode(name, this)
|
|
99
|
-
if (useFastRender(this, klass)) {
|
|
100
|
-
connectNode(name, this)
|
|
101
|
-
} else {
|
|
102
|
-
requestAnimationFrame(()=>{
|
|
103
|
-
connectNode(name, this)
|
|
104
|
-
})
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
})
|
|
158
|
+
// Handle CSS (can be string or function)
|
|
159
|
+
if (instance.CSS) {
|
|
160
|
+
newKlass.css =
|
|
161
|
+
typeof instance.CSS === "function" ? instance.CSS() : instance.CSS;
|
|
108
162
|
}
|
|
109
|
-
}
|
|
110
163
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
164
|
+
// Handle HTML (can be string or function)
|
|
165
|
+
if (instance.HTML) {
|
|
166
|
+
const html =
|
|
167
|
+
typeof instance.HTML === "function" ? instance.HTML() : instance.HTML;
|
|
168
|
+
newKlass.html = closeCustomTags(html);
|
|
116
169
|
}
|
|
117
|
-
|
|
118
|
-
|
|
170
|
+
|
|
171
|
+
// Handle META (generic metadata object)
|
|
172
|
+
if (instance.META) {
|
|
173
|
+
newKlass.META = instance.META;
|
|
174
|
+
Fez.index.ensure(name).meta = instance.META;
|
|
119
175
|
}
|
|
120
|
-
|
|
121
|
-
|
|
176
|
+
|
|
177
|
+
// Auto-mount global components
|
|
178
|
+
if (instance.GLOBAL) {
|
|
179
|
+
Fez.onReady(() => document.body.appendChild(document.createElement(name)));
|
|
122
180
|
}
|
|
181
|
+
|
|
182
|
+
Fez.consoleLog(`${name} compiled`);
|
|
183
|
+
return newKlass;
|
|
123
184
|
}
|
|
124
185
|
|
|
125
186
|
/**
|
|
126
|
-
*
|
|
127
|
-
*
|
|
187
|
+
* Convert self-closing custom tags to full format
|
|
188
|
+
* <my-comp /> -> <my-comp></my-comp>
|
|
189
|
+
* Uses (?:[^>]|=>) to skip => (arrow functions) inside attributes
|
|
128
190
|
*/
|
|
129
191
|
function closeCustomTags(html) {
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
192
|
+
return html.replace(
|
|
193
|
+
/<([a-z][a-z-]*)\b((?:=>|[^>])*)>/g,
|
|
194
|
+
(match, tag, attrs) => {
|
|
195
|
+
if (!attrs.trimEnd().endsWith("/")) return match;
|
|
196
|
+
if (SELF_CLOSING_TAGS.has(tag)) return match;
|
|
197
|
+
return `<${tag}${attrs.replace(/\s*\/$/, "")}></${tag}>`;
|
|
198
|
+
},
|
|
199
|
+
);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Determine if component should render synchronously
|
|
204
|
+
*/
|
|
205
|
+
function shouldRenderFast(node, klass) {
|
|
206
|
+
const attr = node.getAttribute("fez-fast");
|
|
207
|
+
if (attr === "false") return false;
|
|
133
208
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
209
|
+
const klassFast =
|
|
210
|
+
typeof klass.FAST === "function" ? klass.FAST(node) : klass.FAST;
|
|
211
|
+
return !!(attr || klassFast || node.childNodes[0] || node.nextSibling);
|
|
137
212
|
}
|
|
138
213
|
|
|
214
|
+
// =============================================================================
|
|
215
|
+
// NODE CONNECTION (Instantiation)
|
|
216
|
+
// =============================================================================
|
|
217
|
+
|
|
139
218
|
/**
|
|
140
|
-
*
|
|
141
|
-
* Replaces the custom element with the component's rendered content
|
|
219
|
+
* Initialize component instance from DOM node
|
|
142
220
|
*/
|
|
143
221
|
function connectNode(name, node) {
|
|
144
|
-
|
|
145
|
-
|
|
222
|
+
if (!node.isConnected) return;
|
|
223
|
+
if (node.classList?.contains("fez")) return;
|
|
146
224
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
225
|
+
const klass = Fez.index[name]?.class;
|
|
226
|
+
const nodeName =
|
|
227
|
+
typeof klass.nodeName === "function"
|
|
228
|
+
? klass.nodeName(node)
|
|
229
|
+
: klass.nodeName;
|
|
230
|
+
const newNode = document.createElement(nodeName || "div");
|
|
150
231
|
|
|
151
|
-
|
|
152
|
-
newNode.classList.add(`fez-${name}`)
|
|
232
|
+
newNode.classList.add("fez", `fez-${name}`);
|
|
153
233
|
|
|
154
|
-
|
|
234
|
+
if (!node.parentNode) {
|
|
235
|
+
console.warn(`Fez: ${name} has no parent, skipping`);
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
155
238
|
|
|
156
|
-
|
|
239
|
+
// Replace custom element with component node
|
|
240
|
+
node.parentNode.replaceChild(newNode, node);
|
|
157
241
|
|
|
158
|
-
|
|
159
|
-
|
|
242
|
+
// Create instance
|
|
243
|
+
const fez = new klass();
|
|
244
|
+
fez.UID = ++Fez.instanceCount;
|
|
245
|
+
Fez.instances.set(fez.UID, fez);
|
|
160
246
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
247
|
+
fez.oldRoot = node;
|
|
248
|
+
fez.fezName = name;
|
|
249
|
+
fez.root = newNode;
|
|
250
|
+
fez.props = klass.getProps(node, newNode);
|
|
251
|
+
fez.class = klass;
|
|
166
252
|
|
|
167
|
-
|
|
168
|
-
|
|
253
|
+
// Move children (slot content)
|
|
254
|
+
fez.fezSlot(node, newNode);
|
|
169
255
|
|
|
170
|
-
|
|
256
|
+
newNode.fez = fez;
|
|
171
257
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
258
|
+
// Global component reference
|
|
259
|
+
if (klass.GLOBAL && klass.GLOBAL !== true) {
|
|
260
|
+
window[klass.GLOBAL] ||= fez;
|
|
261
|
+
}
|
|
175
262
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
}
|
|
263
|
+
// jQuery compatibility
|
|
264
|
+
if (window.$) fez.$root = $(newNode);
|
|
179
265
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
266
|
+
// Copy ID
|
|
267
|
+
if (fez.props.id) newNode.setAttribute("id", fez.props.id);
|
|
268
|
+
|
|
269
|
+
// Copy fez-keep for DOM differ preservation
|
|
270
|
+
const fezKeep = node.getAttribute("fez-keep");
|
|
271
|
+
if (fezKeep) newNode.setAttribute("fez-keep", fezKeep);
|
|
272
|
+
|
|
273
|
+
// === LIFECYCLE ===
|
|
274
|
+
|
|
275
|
+
// Setup reactive state
|
|
276
|
+
fez.fezRegister();
|
|
277
|
+
|
|
278
|
+
// Capture children before rendering replaces them
|
|
279
|
+
if (fez.root.childNodes.length) {
|
|
280
|
+
fez._fezSlotNodes = Array.from(fez.root.childNodes);
|
|
281
|
+
fez._fezChildNodes = fez._fezSlotNodes.filter((n) => n.nodeType === 1);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// Prevent state changes during init/mount from scheduling extra renders
|
|
285
|
+
fez._isInitializing = true;
|
|
183
286
|
|
|
184
|
-
|
|
185
|
-
|
|
287
|
+
// Init (supports multiple naming conventions)
|
|
288
|
+
const initMethod = fez.onInit || fez.init || fez.created || fez.connect;
|
|
289
|
+
initMethod.call(fez, fez.props);
|
|
186
290
|
|
|
187
|
-
|
|
188
|
-
|
|
291
|
+
// Render
|
|
292
|
+
fez.fezRender();
|
|
189
293
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
fez.firstRender = true
|
|
294
|
+
// Done initializing - state changes in onMount will now trigger renders
|
|
295
|
+
fez._isInitializing = false;
|
|
193
296
|
|
|
194
|
-
|
|
195
|
-
|
|
297
|
+
// Mount
|
|
298
|
+
fez.onMount(fez.props);
|
|
196
299
|
|
|
197
|
-
|
|
198
|
-
|
|
300
|
+
// Form submit handling
|
|
301
|
+
if (fez.onSubmit) {
|
|
302
|
+
const form = fez.root.nodeName === "FORM" ? fez.root : fez.find("form");
|
|
303
|
+
if (form) {
|
|
199
304
|
form.onsubmit = (e) => {
|
|
200
|
-
e.preventDefault()
|
|
201
|
-
fez.onSubmit(fez.formData())
|
|
202
|
-
}
|
|
305
|
+
e.preventDefault();
|
|
306
|
+
fez.onSubmit(fez.formData());
|
|
307
|
+
};
|
|
203
308
|
}
|
|
309
|
+
}
|
|
204
310
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
for (const [key, value] of Object.entries(fez.props)) {
|
|
211
|
-
fez.onPropsChange(key, value)
|
|
212
|
-
}
|
|
311
|
+
// Watch for attribute changes
|
|
312
|
+
if (fez.onPropsChange) {
|
|
313
|
+
attrObserver.observe(newNode, { attributes: true });
|
|
314
|
+
for (const [key, value] of Object.entries(fez.props)) {
|
|
315
|
+
fez.onPropsChange(key, value);
|
|
213
316
|
}
|
|
214
317
|
}
|
|
215
318
|
}
|