webspresso 0.0.110 → 0.0.112
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/core/client/adapter/datastar.js +190 -51
- package/core/client/api.js +23 -21
- package/core/client/controller.js +407 -40
- package/core/client/debug.js +12 -12
- package/core/client/devtool.js +124 -19
- package/core/client/forms.js +163 -29
- package/core/client/fragments.js +616 -118
- package/core/client/host.js +369 -0
- package/core/client/index.d.ts +18 -0
- package/core/client/index.js +154 -23
- package/core/client/lite.js +101 -4
- package/core/client/server-fragments.js +142 -22
- package/core/client/services.js +588 -0
- package/core/client/utils.js +188 -2
- package/index.d.ts +334 -29
- package/index.js +3 -0
- package/package.json +1 -1
- package/src/bundle/index.js +175 -14
- package/src/dev/banner.js +4 -1
- package/src/dev-reload/index.js +29 -1
- package/src/server.js +28 -0
package/core/client/utils.js
CHANGED
|
@@ -5,6 +5,22 @@
|
|
|
5
5
|
* Zero-dependency, lightweight helpers to avoid duplicate logic across client modules.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
+
/**
|
|
9
|
+
* Check if a value is an Array.
|
|
10
|
+
* @param {unknown} val
|
|
11
|
+
* @returns {val is any[]}
|
|
12
|
+
*/
|
|
13
|
+
const isArray = Array.isArray;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Check if a value is a finite number (`typeof val === 'number' && Number.isFinite(val)`).
|
|
17
|
+
* @param {unknown} val
|
|
18
|
+
* @returns {val is number}
|
|
19
|
+
*/
|
|
20
|
+
function isNumber(val) {
|
|
21
|
+
return typeof val === 'number' && Number.isFinite(val);
|
|
22
|
+
}
|
|
23
|
+
|
|
8
24
|
/**
|
|
9
25
|
* Check if a value is a non-null object.
|
|
10
26
|
* @param {unknown} val
|
|
@@ -20,7 +36,7 @@ function isObject(val) {
|
|
|
20
36
|
* @returns {boolean}
|
|
21
37
|
*/
|
|
22
38
|
function isPlainObject(val) {
|
|
23
|
-
return isObject(val) && !
|
|
39
|
+
return isObject(val) && !isArray(val) && Object.prototype.toString.call(val) === '[object Object]';
|
|
24
40
|
}
|
|
25
41
|
|
|
26
42
|
/**
|
|
@@ -169,7 +185,7 @@ function parseDataValue(val) {
|
|
|
169
185
|
if (val === 'undefined') return undefined;
|
|
170
186
|
if (/^-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?$/.test(val)) {
|
|
171
187
|
const num = Number(val);
|
|
172
|
-
if (
|
|
188
|
+
if (isNumber(num)) return num;
|
|
173
189
|
}
|
|
174
190
|
if ((val.startsWith('{') && val.endsWith('}')) || (val.startsWith('[') && val.endsWith(']'))) {
|
|
175
191
|
try {
|
|
@@ -265,7 +281,172 @@ function decorateElement(el) {
|
|
|
265
281
|
return el;
|
|
266
282
|
}
|
|
267
283
|
|
|
284
|
+
/**
|
|
285
|
+
* Parse a duration value (number in ms, numeric string, or human duration string like '500ms', '10s', '2m', '1h')
|
|
286
|
+
* into milliseconds. Returns `defaultMs` (0) when false/null/undefined/invalid.
|
|
287
|
+
*
|
|
288
|
+
* @param {unknown} val
|
|
289
|
+
* @param {number} [defaultMs=0]
|
|
290
|
+
* @returns {number}
|
|
291
|
+
*/
|
|
292
|
+
function parseDurationMs(val, defaultMs = 0) {
|
|
293
|
+
if (val === false || val === null || val === undefined || val === '' || val === 'false') {
|
|
294
|
+
return 0;
|
|
295
|
+
}
|
|
296
|
+
if (isNumber(val)) {
|
|
297
|
+
return val > 0 ? Math.floor(val) : 0;
|
|
298
|
+
}
|
|
299
|
+
if (typeof val === 'string') {
|
|
300
|
+
const trimmed = val.trim().toLowerCase();
|
|
301
|
+
if (!trimmed || trimmed === 'false' || trimmed === '0') return 0;
|
|
302
|
+
const match = /^(\d+(?:\.\d+)?)\s*(ms|s|m|h)?$/.exec(trimmed);
|
|
303
|
+
if (!match) return defaultMs;
|
|
304
|
+
const num = parseFloat(match[1]);
|
|
305
|
+
if (!isNumber(num) || num <= 0) return 0;
|
|
306
|
+
const unit = match[2] || 'ms';
|
|
307
|
+
if (unit === 's') return Math.floor(num * 1000);
|
|
308
|
+
if (unit === 'm') return Math.floor(num * 60 * 1000);
|
|
309
|
+
if (unit === 'h') return Math.floor(num * 60 * 60 * 1000);
|
|
310
|
+
return Math.floor(num);
|
|
311
|
+
}
|
|
312
|
+
return defaultMs;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* Normalize a React Query-style refetch policy (`true`, `false`, `'always'`, `'true'`, `'false'`).
|
|
317
|
+
*
|
|
318
|
+
* @param {unknown} val
|
|
319
|
+
* @param {boolean|'always'} [defaultMode=false]
|
|
320
|
+
* @returns {boolean|'always'}
|
|
321
|
+
*/
|
|
322
|
+
function parseRefetchMode(val, defaultMode = false) {
|
|
323
|
+
if (val === undefined || val === null || val === '') {
|
|
324
|
+
return defaultMode;
|
|
325
|
+
}
|
|
326
|
+
if (val === 'always') return 'always';
|
|
327
|
+
if (val === true || val === 'true' || val === '1') return true;
|
|
328
|
+
if (val === false || val === 'false' || val === '0') return false;
|
|
329
|
+
return Boolean(val);
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
/**
|
|
333
|
+
* Generate a short base-36 random token (`Math.random().toString(36).slice(2, 2 + len)`).
|
|
334
|
+
*
|
|
335
|
+
* @param {number} [len=5]
|
|
336
|
+
* @returns {string}
|
|
337
|
+
*/
|
|
338
|
+
function randomId(len = 5) {
|
|
339
|
+
return Math.random().toString(36).slice(2, 2 + len);
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
/**
|
|
343
|
+
* Generate a timestamped unique ID with an optional prefix (e.g. `uid('evt')` -> `'evt_1730000000_a1b2c'`).
|
|
344
|
+
*
|
|
345
|
+
* @param {string} [prefix='id']
|
|
346
|
+
* @param {number} [len=5]
|
|
347
|
+
* @returns {string}
|
|
348
|
+
*/
|
|
349
|
+
function uid(prefix = 'id', len = 5) {
|
|
350
|
+
return `${prefix}_${Date.now()}_${randomId(len)}`;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
/**
|
|
354
|
+
* Apply or restore automatic busy/loading state (`data-w-busy`, `aria-busy`, `disabled`,
|
|
355
|
+
* `data-w-loading-text` / `data-w-disable-with`, and `data-w-loading-class`) on an interactive element.
|
|
356
|
+
*
|
|
357
|
+
* @param {Element|null|undefined} el
|
|
358
|
+
* @param {boolean} isBusy
|
|
359
|
+
*/
|
|
360
|
+
function setElementBusyState(el, isBusy) {
|
|
361
|
+
if (!el || typeof el.setAttribute !== 'function') return;
|
|
362
|
+
|
|
363
|
+
if (isBusy) {
|
|
364
|
+
el.__wBusyCount = (el.__wBusyCount || 0) + 1;
|
|
365
|
+
if (el.__wBusyCount > 1) return;
|
|
366
|
+
|
|
367
|
+
el.__wActionBusy = true;
|
|
368
|
+
el.setAttribute('data-w-busy', 'true');
|
|
369
|
+
el.setAttribute('aria-busy', 'true');
|
|
370
|
+
|
|
371
|
+
const tag = el.tagName || '';
|
|
372
|
+
const isInteractive =
|
|
373
|
+
tag === 'BUTTON' ||
|
|
374
|
+
tag === 'INPUT' ||
|
|
375
|
+
tag === 'SELECT' ||
|
|
376
|
+
tag === 'TEXTAREA' ||
|
|
377
|
+
'disabled' in el ||
|
|
378
|
+
el.hasAttribute('data-w-loading-text') ||
|
|
379
|
+
el.hasAttribute('data-w-disable-with');
|
|
380
|
+
|
|
381
|
+
if (isInteractive && !el.hasAttribute('data-w-disabled') && !el.hasAttribute('data-w-no-disable')) {
|
|
382
|
+
el.__wWasDisabled = Boolean(el.disabled);
|
|
383
|
+
el.__wHadDisabledAttr = el.hasAttribute('disabled');
|
|
384
|
+
el.disabled = true;
|
|
385
|
+
el.setAttribute('disabled', '');
|
|
386
|
+
el.setAttribute('aria-disabled', 'true');
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
const loadingText = el.getAttribute('data-w-loading-text') ?? el.getAttribute('data-w-disable-with');
|
|
390
|
+
if (loadingText !== null) {
|
|
391
|
+
if (tag === 'INPUT' && (el.type === 'submit' || el.type === 'button')) {
|
|
392
|
+
el.__wOrigValue = el.value;
|
|
393
|
+
el.value = loadingText;
|
|
394
|
+
} else {
|
|
395
|
+
el.__wOrigText = el.textContent;
|
|
396
|
+
el.textContent = loadingText;
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
const loadingClass = safeTrim(el.getAttribute('data-w-loading-class'));
|
|
401
|
+
if (loadingClass && el.classList) {
|
|
402
|
+
const classes = loadingClass.split(/\s+/).filter(Boolean);
|
|
403
|
+
el.__wAddedClasses = classes;
|
|
404
|
+
for (let i = 0; i < classes.length; i++) {
|
|
405
|
+
el.classList.add(classes[i]);
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
} else {
|
|
409
|
+
if (!el.__wBusyCount) return;
|
|
410
|
+
el.__wBusyCount -= 1;
|
|
411
|
+
if (el.__wBusyCount > 0) return;
|
|
412
|
+
|
|
413
|
+
el.__wActionBusy = false;
|
|
414
|
+
el.removeAttribute('data-w-busy');
|
|
415
|
+
el.removeAttribute('aria-busy');
|
|
416
|
+
|
|
417
|
+
if (!el.hasAttribute('data-w-disabled') && !el.hasAttribute('data-w-no-disable')) {
|
|
418
|
+
if (!el.__wWasDisabled) {
|
|
419
|
+
el.disabled = false;
|
|
420
|
+
if (!el.__wHadDisabledAttr) {
|
|
421
|
+
el.removeAttribute('disabled');
|
|
422
|
+
}
|
|
423
|
+
el.removeAttribute('aria-disabled');
|
|
424
|
+
}
|
|
425
|
+
delete el.__wWasDisabled;
|
|
426
|
+
delete el.__wHadDisabledAttr;
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
if (el.__wOrigValue !== undefined) {
|
|
430
|
+
el.value = el.__wOrigValue;
|
|
431
|
+
delete el.__wOrigValue;
|
|
432
|
+
}
|
|
433
|
+
if (el.__wOrigText !== undefined) {
|
|
434
|
+
el.textContent = el.__wOrigText;
|
|
435
|
+
delete el.__wOrigText;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
if (el.__wAddedClasses && el.classList) {
|
|
439
|
+
for (let i = 0; i < el.__wAddedClasses.length; i++) {
|
|
440
|
+
el.classList.remove(el.__wAddedClasses[i]);
|
|
441
|
+
}
|
|
442
|
+
delete el.__wAddedClasses;
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
|
|
268
447
|
module.exports = {
|
|
448
|
+
isArray,
|
|
449
|
+
isNumber,
|
|
269
450
|
isObject,
|
|
270
451
|
isPlainObject,
|
|
271
452
|
isPromise,
|
|
@@ -277,5 +458,10 @@ module.exports = {
|
|
|
277
458
|
toKebabCase,
|
|
278
459
|
parseDataValue,
|
|
279
460
|
decorateElement,
|
|
461
|
+
parseDurationMs,
|
|
462
|
+
parseRefetchMode,
|
|
463
|
+
randomId,
|
|
464
|
+
uid,
|
|
465
|
+
setElementBusyState,
|
|
280
466
|
};
|
|
281
467
|
|