@dinoreic/fez 0.3.2 → 0.4.0
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 +5 -5
- package/dist/fez.js +44 -21
- package/dist/fez.js.map +4 -4
- package/package.json +2 -2
- package/src/fez/compile.js +4 -7
- package/src/fez/connect.js +50 -77
- package/src/fez/instance.js +59 -10
- package/src/fez/root.js +11 -174
- package/src/fez/utility.js +198 -2
- package/src/fez/utils/css_mixin.js +25 -0
- package/src/fez/utils/dump.js +44 -15
- package/src/fez/utils/highlight_all.js +98 -0
- package/src/svelte-cde-adapter.coffee +122 -0
package/src/fez/utility.js
CHANGED
|
@@ -46,7 +46,7 @@ export default (Fez) => {
|
|
|
46
46
|
script.type = 'module';
|
|
47
47
|
script.textContent = config.script;
|
|
48
48
|
document.head.appendChild(script);
|
|
49
|
-
|
|
49
|
+
requestAnimationFrame(()=>script.remove())
|
|
50
50
|
} else {
|
|
51
51
|
try {
|
|
52
52
|
new Function(config.script)();
|
|
@@ -121,6 +121,110 @@ export default (Fez) => {
|
|
|
121
121
|
return element;
|
|
122
122
|
}
|
|
123
123
|
|
|
124
|
+
// Fetch wrapper with automatic caching and data handling
|
|
125
|
+
// Usage:
|
|
126
|
+
// Fez.fetch(url) - GET request (default)
|
|
127
|
+
// Fez.fetch(url, callback) - GET with callback
|
|
128
|
+
// Fez.fetch(url, data) - GET with query params (?foo=bar&baz=qux)
|
|
129
|
+
// Fez.fetch(url, data, callback) - GET with query params and callback
|
|
130
|
+
// Fez.fetch('POST', url, data) - POST with FormData body (multipart/form-data)
|
|
131
|
+
// Fez.fetch('POST', url, data, callback) - POST with FormData and callback
|
|
132
|
+
// Data object is automatically converted:
|
|
133
|
+
// - GET: appended as URL query parameters
|
|
134
|
+
// - POST: sent as FormData (multipart/form-data) without custom headers
|
|
135
|
+
Fez.fetch = function(...args) {
|
|
136
|
+
// Initialize cache if not exists
|
|
137
|
+
Fez._fetchCache ||= {};
|
|
138
|
+
|
|
139
|
+
let method = 'GET';
|
|
140
|
+
let url;
|
|
141
|
+
let callback;
|
|
142
|
+
|
|
143
|
+
// Check if first arg is HTTP method (uppercase letters)
|
|
144
|
+
if (typeof args[0] === 'string' && /^[A-Z]+$/.test(args[0])) {
|
|
145
|
+
method = args.shift();
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// URL is required
|
|
149
|
+
url = args.shift();
|
|
150
|
+
|
|
151
|
+
// Check for data/options object
|
|
152
|
+
let opts = {};
|
|
153
|
+
let data = null;
|
|
154
|
+
if (typeof args[0] === 'object') {
|
|
155
|
+
data = args.shift();
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Check for callback function
|
|
159
|
+
if (typeof args[0] === 'function') {
|
|
160
|
+
callback = args.shift();
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Handle data based on method
|
|
164
|
+
if (data) {
|
|
165
|
+
if (method === 'GET') {
|
|
166
|
+
// For GET, append data as query parameters
|
|
167
|
+
const params = new URLSearchParams(data);
|
|
168
|
+
url += (url.includes('?') ? '&' : '?') + params.toString();
|
|
169
|
+
} else if (method === 'POST') {
|
|
170
|
+
// For POST, convert to FormData
|
|
171
|
+
const formData = new FormData();
|
|
172
|
+
for (const [key, value] of Object.entries(data)) {
|
|
173
|
+
formData.append(key, value);
|
|
174
|
+
}
|
|
175
|
+
opts.body = formData;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Set method
|
|
180
|
+
opts.method = method;
|
|
181
|
+
|
|
182
|
+
// Create cache key from method, url, and stringified opts
|
|
183
|
+
const cacheKey = `${method}:${url}:${JSON.stringify(opts)}`;
|
|
184
|
+
|
|
185
|
+
// Check cache first
|
|
186
|
+
if (Fez._fetchCache[cacheKey]) {
|
|
187
|
+
const cachedData = Fez._fetchCache[cacheKey];
|
|
188
|
+
Fez.log(`fetch cache hit: ${method} ${url}`);
|
|
189
|
+
if (callback) {
|
|
190
|
+
callback(cachedData);
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
return Promise.resolve(cachedData);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Log live fetch
|
|
197
|
+
Fez.log(`fetch live: ${method} ${url}`);
|
|
198
|
+
|
|
199
|
+
// Helper to process and cache response
|
|
200
|
+
const processResponse = (response) => {
|
|
201
|
+
if (response.headers.get('content-type')?.includes('application/json')) {
|
|
202
|
+
return response.json();
|
|
203
|
+
}
|
|
204
|
+
return response.text();
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
// If callback provided, execute and handle
|
|
208
|
+
if (callback) {
|
|
209
|
+
fetch(url, opts)
|
|
210
|
+
.then(processResponse)
|
|
211
|
+
.then(data => {
|
|
212
|
+
Fez._fetchCache[cacheKey] = data;
|
|
213
|
+
callback(data);
|
|
214
|
+
})
|
|
215
|
+
.catch(error => Fez.onError('fetch', error));
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Return promise with automatic JSON parsing
|
|
220
|
+
return fetch(url, opts)
|
|
221
|
+
.then(processResponse)
|
|
222
|
+
.then(data => {
|
|
223
|
+
Fez._fetchCache[cacheKey] = data;
|
|
224
|
+
return data;
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
|
|
124
228
|
Fez.darkenColor = (color, percent = 20) => {
|
|
125
229
|
// Convert hex to RGB
|
|
126
230
|
const num = parseInt(color.replace("#", ""), 16)
|
|
@@ -191,7 +295,99 @@ export default (Fez) => {
|
|
|
191
295
|
return pointer;
|
|
192
296
|
}
|
|
193
297
|
else if (typeof pointer === 'string') {
|
|
194
|
-
|
|
298
|
+
// Check if it's a function expression (arrow function or function keyword)
|
|
299
|
+
// Arrow function: (args) => or args =>
|
|
300
|
+
const arrowFuncPattern = /^\s*\(?\s*\w+(\s*,\s*\w+)*\s*\)?\s*=>/;
|
|
301
|
+
const functionPattern = /^\s*function\s*\(/;
|
|
302
|
+
|
|
303
|
+
if (arrowFuncPattern.test(pointer) || functionPattern.test(pointer)) {
|
|
304
|
+
return new Function('return ' + pointer)();
|
|
305
|
+
} else if (pointer.includes('.') && !pointer.includes('(')) {
|
|
306
|
+
// It's a property access like "this.focus" - return a function that calls it
|
|
307
|
+
return new Function(`return function() { return ${pointer}(); }`);
|
|
308
|
+
} else {
|
|
309
|
+
// It's a function body
|
|
310
|
+
return new Function(pointer);
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// Execute a function when DOM is ready or immediately if already loaded
|
|
316
|
+
Fez.onReady = (callback) => {
|
|
317
|
+
if (document.readyState === 'loading') {
|
|
318
|
+
document.addEventListener('DOMContentLoaded', ()=>{
|
|
319
|
+
callback()
|
|
320
|
+
}, { once: true })
|
|
321
|
+
} else {
|
|
322
|
+
callback()
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// get unique id from string
|
|
327
|
+
Fez.fnv1 = (str) => {
|
|
328
|
+
var FNV_OFFSET_BASIS, FNV_PRIME, hash, i, j, ref;
|
|
329
|
+
FNV_OFFSET_BASIS = 2166136261;
|
|
330
|
+
FNV_PRIME = 16777619;
|
|
331
|
+
hash = FNV_OFFSET_BASIS;
|
|
332
|
+
for (i = j = 0, ref = str.length - 1; (0 <= ref ? j <= ref : j >= ref); i = 0 <= ref ? ++j : --j) {
|
|
333
|
+
hash ^= str.charCodeAt(i);
|
|
334
|
+
hash *= FNV_PRIME;
|
|
335
|
+
}
|
|
336
|
+
return hash.toString(36).replaceAll('-', '');
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
Fez.tag = (tag, opts = {}, html = '') => {
|
|
340
|
+
const json = encodeURIComponent(JSON.stringify(opts))
|
|
341
|
+
return `<${tag} data-props="${json}">${html}</${tag}>`
|
|
342
|
+
// const json = JSON.stringify(opts, null, 2)
|
|
343
|
+
// const data = `<script type="text/template">${json}</script><${tag} data-json-template="true">${html}</${tag}>`
|
|
344
|
+
// return data
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// execute function until it returns true
|
|
348
|
+
Fez.untilTrue = (func, pingRate) => {
|
|
349
|
+
pingRate ||= 200
|
|
350
|
+
|
|
351
|
+
if (!func()) {
|
|
352
|
+
setTimeout(()=>{
|
|
353
|
+
Fez.untilTrue(func, pingRate)
|
|
354
|
+
} ,pingRate)
|
|
195
355
|
}
|
|
196
356
|
}
|
|
357
|
+
|
|
358
|
+
// throttle function calls
|
|
359
|
+
Fez.throttle = (func, delay = 200) => {
|
|
360
|
+
let lastRun = 0;
|
|
361
|
+
let timeout;
|
|
362
|
+
|
|
363
|
+
return function(...args) {
|
|
364
|
+
const now = Date.now();
|
|
365
|
+
|
|
366
|
+
if (now - lastRun >= delay) {
|
|
367
|
+
func.apply(this, args);
|
|
368
|
+
lastRun = now;
|
|
369
|
+
} else {
|
|
370
|
+
clearTimeout(timeout);
|
|
371
|
+
timeout = setTimeout(() => {
|
|
372
|
+
func.apply(this, args);
|
|
373
|
+
lastRun = Date.now();
|
|
374
|
+
}, delay - (now - lastRun));
|
|
375
|
+
}
|
|
376
|
+
};
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
// const firstTimeHash = new Map()
|
|
380
|
+
// Fez.firstTime = (key, func) => {
|
|
381
|
+
// if ( !firstTimeHash.get(key) ) {
|
|
382
|
+
// firstTimeHash.set(key, true)
|
|
383
|
+
|
|
384
|
+
// if (func) {
|
|
385
|
+
// func()
|
|
386
|
+
// }
|
|
387
|
+
|
|
388
|
+
// return true
|
|
389
|
+
// }
|
|
390
|
+
|
|
391
|
+
// return false
|
|
392
|
+
// }
|
|
197
393
|
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// define custom style macro - simple scss mixin
|
|
2
|
+
// :mobile { ... } -> @media (max-width: 768px) { ... }
|
|
3
|
+
// @include mobile { ... } -> @media (max-width: 768px) { ... }
|
|
4
|
+
// demo/fez/ui-style.fez
|
|
5
|
+
|
|
6
|
+
const CssMixins = {}
|
|
7
|
+
|
|
8
|
+
export default (Fez) => {
|
|
9
|
+
Fez.cssMixin = (name, content) => {
|
|
10
|
+
if (content) {
|
|
11
|
+
CssMixins[name] = content
|
|
12
|
+
} else {
|
|
13
|
+
Object.entries(CssMixins).forEach(([key, val])=>{
|
|
14
|
+
name = name.replaceAll(`:${key} `, `${val} `)
|
|
15
|
+
name = name.replaceAll(`@include ${key} `, `${val} `)
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
return name
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
Fez.cssMixin('mobile', '@media (max-width: 767px)')
|
|
23
|
+
Fez.cssMixin('tablet', '@media (min-width: 768px) and (max-width: 1023px)')
|
|
24
|
+
Fez.cssMixin('desktop', '@media (min-width: 1200px)')
|
|
25
|
+
}
|
package/src/fez/utils/dump.js
CHANGED
|
@@ -54,8 +54,9 @@ const LOG = (() => {
|
|
|
54
54
|
const logs = [];
|
|
55
55
|
const logTypes = []; // Track the original type of each log
|
|
56
56
|
let currentIndex = 0;
|
|
57
|
+
let renderContent = null; // Will hold the render function
|
|
57
58
|
|
|
58
|
-
// Add ESC key handler
|
|
59
|
+
// Add ESC key handler and arrow key navigation
|
|
59
60
|
document.addEventListener('keydown', (e) => {
|
|
60
61
|
if (e.key === 'Escape') {
|
|
61
62
|
e.preventDefault();
|
|
@@ -65,14 +66,34 @@ const LOG = (() => {
|
|
|
65
66
|
if (dialog) {
|
|
66
67
|
// Close dialog
|
|
67
68
|
dialog.remove();
|
|
68
|
-
localStorage.setItem('_LOG_CLOSED', 'true');
|
|
69
69
|
createLogButton();
|
|
70
70
|
} else if (button) {
|
|
71
71
|
// Open dialog
|
|
72
72
|
button.remove();
|
|
73
|
-
localStorage.setItem('_LOG_CLOSED', 'false');
|
|
74
73
|
showLogDialog();
|
|
75
74
|
}
|
|
75
|
+
} else if (e.key === 'ArrowLeft' || e.key === 'ArrowRight' || e.key === 'ArrowUp' || e.key === 'ArrowDown') {
|
|
76
|
+
const dialog = document.getElementById('dump-dialog');
|
|
77
|
+
if (dialog && logs.length > 0) {
|
|
78
|
+
e.preventDefault();
|
|
79
|
+
if (e.key === 'ArrowLeft' && currentIndex > 0) {
|
|
80
|
+
currentIndex--;
|
|
81
|
+
localStorage.setItem('_LOG_INDEX', currentIndex);
|
|
82
|
+
renderContent();
|
|
83
|
+
} else if (e.key === 'ArrowRight' && currentIndex < logs.length - 1) {
|
|
84
|
+
currentIndex++;
|
|
85
|
+
localStorage.setItem('_LOG_INDEX', currentIndex);
|
|
86
|
+
renderContent();
|
|
87
|
+
} else if (e.key === 'ArrowUp' && currentIndex > 0) {
|
|
88
|
+
currentIndex = Math.max(0, currentIndex - 5);
|
|
89
|
+
localStorage.setItem('_LOG_INDEX', currentIndex);
|
|
90
|
+
renderContent();
|
|
91
|
+
} else if (e.key === 'ArrowDown' && currentIndex < logs.length - 1) {
|
|
92
|
+
currentIndex = Math.min(logs.length - 1, currentIndex + 5);
|
|
93
|
+
localStorage.setItem('_LOG_INDEX', currentIndex);
|
|
94
|
+
renderContent();
|
|
95
|
+
}
|
|
96
|
+
}
|
|
76
97
|
}
|
|
77
98
|
});
|
|
78
99
|
|
|
@@ -90,7 +111,6 @@ const LOG = (() => {
|
|
|
90
111
|
'opacity:1;visibility:visible;box-shadow:0 4px 12px rgba(255,51,51,0.3)';
|
|
91
112
|
btn.onclick = () => {
|
|
92
113
|
btn.remove();
|
|
93
|
-
localStorage.setItem('_LOG_CLOSED', 'false');
|
|
94
114
|
showLogDialog();
|
|
95
115
|
};
|
|
96
116
|
}
|
|
@@ -117,7 +137,7 @@ const LOG = (() => {
|
|
|
117
137
|
currentIndex = logs.length - 1;
|
|
118
138
|
}
|
|
119
139
|
|
|
120
|
-
|
|
140
|
+
renderContent = () => {
|
|
121
141
|
const buttons = logs.map((_, i) => {
|
|
122
142
|
let bgColor = '#f0f0f0'; // default
|
|
123
143
|
if (i !== currentIndex) {
|
|
@@ -141,7 +161,6 @@ const LOG = (() => {
|
|
|
141
161
|
|
|
142
162
|
d.querySelector('button[style*="flex-shrink:0"]').onclick = () => {
|
|
143
163
|
d.remove();
|
|
144
|
-
localStorage.setItem('_LOG_CLOSED', 'true');
|
|
145
164
|
createLogButton();
|
|
146
165
|
};
|
|
147
166
|
|
|
@@ -163,13 +182,17 @@ const LOG = (() => {
|
|
|
163
182
|
return
|
|
164
183
|
}
|
|
165
184
|
|
|
166
|
-
if (o instanceof Node) {
|
|
167
|
-
o = log_pretty_print(o.outerHTML)
|
|
168
|
-
}
|
|
169
|
-
|
|
170
185
|
// Store the original type
|
|
171
186
|
let originalType = typeof o;
|
|
172
187
|
|
|
188
|
+
if (o instanceof Node) {
|
|
189
|
+
if (o.nodeType === Node.TEXT_NODE) {
|
|
190
|
+
o = o.textContent || String(o)
|
|
191
|
+
} else {
|
|
192
|
+
o = log_pretty_print(o.outerHTML)
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
173
196
|
if (o === undefined) { o = 'undefined' }
|
|
174
197
|
if (o === null) { o = 'null' }
|
|
175
198
|
|
|
@@ -193,13 +216,19 @@ const LOG = (() => {
|
|
|
193
216
|
logs.push(o + `\n\ntype: ${originalType}`);
|
|
194
217
|
logTypes.push(originalType);
|
|
195
218
|
|
|
196
|
-
// Check if log
|
|
197
|
-
const
|
|
219
|
+
// Check if log dialog is open by checking for element
|
|
220
|
+
const isOpen = !!document.getElementById('dump-dialog');
|
|
198
221
|
|
|
199
|
-
if (
|
|
200
|
-
|
|
201
|
-
} else {
|
|
222
|
+
if (!isOpen) {
|
|
223
|
+
// Show log dialog by default
|
|
202
224
|
showLogDialog();
|
|
225
|
+
} else {
|
|
226
|
+
// Update current index to the new log and refresh
|
|
227
|
+
currentIndex = logs.length - 1;
|
|
228
|
+
localStorage.setItem('_LOG_INDEX', currentIndex);
|
|
229
|
+
if (renderContent) {
|
|
230
|
+
renderContent();
|
|
231
|
+
}
|
|
203
232
|
}
|
|
204
233
|
};
|
|
205
234
|
})();
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
// Highlight all Fez elements with their names
|
|
2
|
+
const highlightAll = () => {
|
|
3
|
+
// Only work if Fez.DEV is true OR (port is above 2999 and Fez.DEV is not false)
|
|
4
|
+
const port = parseInt(window.location.port) || 80;
|
|
5
|
+
if (!(Fez.DEV === true || (port > 2999 && Fez.DEV !== false))) return;
|
|
6
|
+
|
|
7
|
+
// Check if highlights already exist
|
|
8
|
+
const existingHighlights = document.querySelectorAll('.fez-highlight-overlay');
|
|
9
|
+
|
|
10
|
+
if (existingHighlights.length > 0) {
|
|
11
|
+
// Remove existing highlights
|
|
12
|
+
existingHighlights.forEach(el => el.remove());
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// Find all Fez and Svelte elements
|
|
17
|
+
const allElements = document.querySelectorAll('.fez, .svelte');
|
|
18
|
+
|
|
19
|
+
allElements.forEach(el => {
|
|
20
|
+
let componentName = null;
|
|
21
|
+
let componentType = null;
|
|
22
|
+
|
|
23
|
+
// Check for Fez component
|
|
24
|
+
if (el.classList.contains('fez') && el.fez && el.fez.fezName) {
|
|
25
|
+
componentName = el.fez.fezName;
|
|
26
|
+
componentType = 'fez';
|
|
27
|
+
}
|
|
28
|
+
// Check for Svelte component
|
|
29
|
+
else if (el.classList.contains('svelte') && el.svelte && el.svelte.svelteName) {
|
|
30
|
+
componentName = el.svelte.svelteName;
|
|
31
|
+
componentType = 'svelte';
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (componentName) {
|
|
35
|
+
// Create overlay div
|
|
36
|
+
const overlay = document.createElement('div');
|
|
37
|
+
overlay.className = 'fez-highlight-overlay';
|
|
38
|
+
|
|
39
|
+
// Get element position
|
|
40
|
+
const rect = el.getBoundingClientRect();
|
|
41
|
+
const scrollTop = window.pageYOffset || document.documentElement.scrollTop;
|
|
42
|
+
const scrollLeft = window.pageXOffset || document.documentElement.scrollLeft;
|
|
43
|
+
|
|
44
|
+
// Style the overlay
|
|
45
|
+
overlay.style.cssText = `
|
|
46
|
+
position: absolute;
|
|
47
|
+
top: ${rect.top + scrollTop}px;
|
|
48
|
+
left: ${rect.left + scrollLeft}px;
|
|
49
|
+
width: ${rect.width}px;
|
|
50
|
+
height: ${rect.height}px;
|
|
51
|
+
border: 1px solid ${componentType === 'svelte' ? 'blue' : 'red'};
|
|
52
|
+
pointer-events: none;
|
|
53
|
+
z-index: 9999;
|
|
54
|
+
`;
|
|
55
|
+
|
|
56
|
+
// Create label for component name
|
|
57
|
+
const label = document.createElement('div');
|
|
58
|
+
label.textContent = componentName;
|
|
59
|
+
label.style.cssText = `
|
|
60
|
+
position: absolute;
|
|
61
|
+
top: -20px;
|
|
62
|
+
left: 0;
|
|
63
|
+
background: ${componentType === 'svelte' ? 'blue' : 'red'};
|
|
64
|
+
color: white;
|
|
65
|
+
padding: 4px 6px 2px 6px;
|
|
66
|
+
font-size: 14px;
|
|
67
|
+
font-family: monospace;
|
|
68
|
+
line-height: 1;
|
|
69
|
+
white-space: nowrap;
|
|
70
|
+
cursor: pointer;
|
|
71
|
+
pointer-events: auto;
|
|
72
|
+
text-transform: uppercase;
|
|
73
|
+
`;
|
|
74
|
+
|
|
75
|
+
// Add click handler to dump the node
|
|
76
|
+
label.addEventListener('click', (e) => {
|
|
77
|
+
e.stopPropagation();
|
|
78
|
+
Fez.dump(el);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
overlay.appendChild(label);
|
|
82
|
+
document.body.appendChild(overlay);
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Bind Ctrl+E to highlightAll
|
|
88
|
+
document.addEventListener('keydown', (event) => {
|
|
89
|
+
if ((event.ctrlKey || event.metaKey) && event.key === 'e') {
|
|
90
|
+
// Check if target is not inside a form
|
|
91
|
+
if (!event.target.closest('form')) {
|
|
92
|
+
event.preventDefault();
|
|
93
|
+
highlightAll();
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
export default highlightAll
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
# get node attributes
|
|
2
|
+
getAttributes = (node) ->
|
|
3
|
+
attrs = {}
|
|
4
|
+
|
|
5
|
+
for el in node.attributes
|
|
6
|
+
if el.name.startsWith('on') && el.value[0] == '('
|
|
7
|
+
el.value = new Function('arg1, arg2', "(#{el.value})(arg1, arg2)")
|
|
8
|
+
if el.name.startsWith(':')
|
|
9
|
+
el.value = new Function("return (#{el.value})").bind(node)()
|
|
10
|
+
el.name = el.name.replace(':', '')
|
|
11
|
+
attrs[el.name] = el.value
|
|
12
|
+
|
|
13
|
+
if attrs['data-props']
|
|
14
|
+
attrs = JSON.parse(attrs['data-props'])
|
|
15
|
+
|
|
16
|
+
# pass props as json template
|
|
17
|
+
# <script type="text/template">{...}</script>
|
|
18
|
+
# <foo-bar data-json-template="true"></foo-bar>
|
|
19
|
+
if attrs['data-json-template']
|
|
20
|
+
prev = node.previousSibling
|
|
21
|
+
if prev?.textContent
|
|
22
|
+
attrs = JSON.parse(prev.textContent)
|
|
23
|
+
prev.remove()
|
|
24
|
+
|
|
25
|
+
# data = node.previousSibling?.textContent
|
|
26
|
+
# if data
|
|
27
|
+
# attrs = JSON.parse(data)
|
|
28
|
+
|
|
29
|
+
attrs
|
|
30
|
+
|
|
31
|
+
# passes root, use `Svelte(this.root.id)` or `this.root.svelte` to get node pointer
|
|
32
|
+
# export onMount(instance, list, node) if you want to do stuff on mount
|
|
33
|
+
# connect DOM node to Svelte class instance
|
|
34
|
+
connect = (node, name, klass) ->
|
|
35
|
+
return unless node.isConnected
|
|
36
|
+
|
|
37
|
+
# TODO: get node name
|
|
38
|
+
# (new klass(target: document.createElement('div'))).nodeName
|
|
39
|
+
exported = Object.getOwnPropertyNames(klass.prototype)
|
|
40
|
+
newNode = document.createElement(if exported.includes('nodeNameSpan') then 'span' else 'div')
|
|
41
|
+
newNode.classList.add('svelte')
|
|
42
|
+
newNode.classList.add("svelte-#{name}")
|
|
43
|
+
newNode.id = node.id || "svelte_#{++Svelte.count}"
|
|
44
|
+
|
|
45
|
+
# get attributes
|
|
46
|
+
props = getAttributes(node)
|
|
47
|
+
props.root ||= newNode
|
|
48
|
+
props.oldRoot ||= node
|
|
49
|
+
props.html ||= node.innerHTML
|
|
50
|
+
|
|
51
|
+
# has to come after getAttributes
|
|
52
|
+
node.parentNode.replaceChild(newNode, node);
|
|
53
|
+
|
|
54
|
+
# bind node and pass all props as single props attribute
|
|
55
|
+
exported = Reflect.ownKeys klass.prototype
|
|
56
|
+
svelteProps = {}
|
|
57
|
+
svelteProps.fast = true if exported.includes('fast') || exported.includes('FAST')
|
|
58
|
+
svelteProps.props = props if exported.includes('props')
|
|
59
|
+
svelteProps.root = newNode if exported.includes('root')
|
|
60
|
+
svelteProps.self = "Svelte('#{newNode.id}')" if exported.includes('self')
|
|
61
|
+
|
|
62
|
+
instance = newNode.svelte = new klass({target: newNode, props: svelteProps})
|
|
63
|
+
instance.svelteName = name
|
|
64
|
+
|
|
65
|
+
# fill slots
|
|
66
|
+
# slot has to be named sslot (not slot)
|
|
67
|
+
if slot = newNode.querySelector('sslot')
|
|
68
|
+
while node.firstChild
|
|
69
|
+
slot.parentNode.insertBefore(node.lastChild, slot.nextSibling);
|
|
70
|
+
slot.parentNode.removeChild(slot)
|
|
71
|
+
|
|
72
|
+
# in the end, call onmount
|
|
73
|
+
if instance.onMount
|
|
74
|
+
list = Array.from node.querySelectorAll(':scope > *')
|
|
75
|
+
instance.onMount(instance, list, node)
|
|
76
|
+
|
|
77
|
+
# # #
|
|
78
|
+
|
|
79
|
+
# passes root, use `Svelte(this.root.id)` or `this.root.svelte` to get node pointer
|
|
80
|
+
# export onMount(instance, list, node) if you want to do stuff on mount
|
|
81
|
+
# Svelte(node || node_id).toogle()
|
|
82
|
+
Svelte = (name, func) ->
|
|
83
|
+
if name.nodeName
|
|
84
|
+
# Svelte(this).close() -> return first parent svelte node
|
|
85
|
+
while name = name.parentNode
|
|
86
|
+
return name.svelte if name.svelte
|
|
87
|
+
else
|
|
88
|
+
name = '#' + name if name[0] != '#' && name[0] != '.'
|
|
89
|
+
document.querySelector(name)?.svelte
|
|
90
|
+
|
|
91
|
+
Svelte.count = 0
|
|
92
|
+
|
|
93
|
+
# Creates custom DOM element
|
|
94
|
+
Svelte.connect = (name, klass) ->
|
|
95
|
+
customElements.define name, class extends HTMLElement
|
|
96
|
+
connectedCallback: ->
|
|
97
|
+
# no not optimize requestAnimationFrame (try to avoid it)
|
|
98
|
+
# because events in nested components are sometimes not propagated at all
|
|
99
|
+
# export let fast
|
|
100
|
+
# %s-menu-vertical{ fast_connect: true }
|
|
101
|
+
|
|
102
|
+
# connect @, name, klass
|
|
103
|
+
# if @firstChild && (klass.prototype.hasOwnProperty('fast') || @getAttribute('fast_connect') || @getAttribute('data-props') || @getAttribute('data-json-template'))
|
|
104
|
+
# connect @, name, klass
|
|
105
|
+
# else
|
|
106
|
+
# requestAnimationFrame =>
|
|
107
|
+
# connect @, name, klass
|
|
108
|
+
|
|
109
|
+
if document.readyState == 'loading'
|
|
110
|
+
document.addEventListener 'DOMContentLoaded',
|
|
111
|
+
=> connect(@, name, klass)
|
|
112
|
+
, once: true
|
|
113
|
+
else
|
|
114
|
+
connect(@, name, klass)
|
|
115
|
+
|
|
116
|
+
# Creates HTML tag
|
|
117
|
+
Svelte.tag = (tag, opts = {}, html = '') ->
|
|
118
|
+
json = JSON.stringify(opts).replaceAll("'", ''')
|
|
119
|
+
"<#{tag} data-props='#{json}'>#{html}</#{tag}>"
|
|
120
|
+
|
|
121
|
+
Svelte.bind = Svelte.connect
|
|
122
|
+
window.Svelte = Svelte
|