jsswift 1.0.51 → 1.0.53
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/AI.md +14 -3
- package/dist/documentions/AI.md +14 -3
- package/dist/documentions/components/table.md +20 -1
- package/dist/documentions/examples/crud.md +14 -1
- package/dist/documentions/knowledge-map.json +1 -1
- package/dist/jsswift.js +328 -231
- package/dist/jsswift.min.js +3 -3
- package/dist/min-jsswift.js +3 -3
- package/package.json +1 -1
package/dist/jsswift.js
CHANGED
|
@@ -661,8 +661,7 @@
|
|
|
661
661
|
const schedulePosition = () => {
|
|
662
662
|
if (!opts.anchorEl) return;
|
|
663
663
|
const raf = globalThis.requestAnimationFrame || ((fn) => setTimeout(() => fn(Date.now()), 0));
|
|
664
|
-
|
|
665
|
-
if (entry._positionFrame != null) caf(entry._positionFrame);
|
|
664
|
+
if (entry._positionFrame != null) return;
|
|
666
665
|
entry._positionFrame = raf(() => {
|
|
667
666
|
entry._positionFrame = null;
|
|
668
667
|
position();
|
|
@@ -673,13 +672,13 @@
|
|
|
673
672
|
if (opts.anchorEl) {
|
|
674
673
|
position();
|
|
675
674
|
schedulePosition();
|
|
676
|
-
const onResize =
|
|
675
|
+
const onResize = schedulePosition;
|
|
677
676
|
window.addEventListener("resize", onResize);
|
|
678
677
|
window.addEventListener("scroll", onResize, true);
|
|
679
678
|
window.visualViewport?.addEventListener?.("resize", onResize);
|
|
680
679
|
window.visualViewport?.addEventListener?.("scroll", onResize);
|
|
681
680
|
if (typeof ResizeObserver === "function") {
|
|
682
|
-
const panelResizeObserver = new ResizeObserver(
|
|
681
|
+
const panelResizeObserver = new ResizeObserver(schedulePosition);
|
|
683
682
|
panelResizeObserver.observe(panel);
|
|
684
683
|
entry._panelResizeObserver = panelResizeObserver;
|
|
685
684
|
}
|
|
@@ -2340,163 +2339,175 @@ _.dynamic = function (renderFn) {
|
|
|
2340
2339
|
|
|
2341
2340
|
return r;
|
|
2342
2341
|
};
|
|
2343
|
-
// ===============================
|
|
2344
|
-
// Two-way binding: input <-> rod
|
|
2345
|
-
// ===============================
|
|
2346
|
-
// ===============================
|
|
2347
|
-
// Two-way binding avanzato
|
|
2348
|
-
// input / checkbox / radio / select
|
|
2349
|
-
// ===============================
|
|
2350
|
-
|
|
2351
|
-
function getInputKind(el) {
|
|
2352
|
-
if (!el || !el.tagName) return null;
|
|
2353
|
-
|
|
2354
|
-
const tag = el.tagName.toLowerCase();
|
|
2355
|
-
|
|
2356
|
-
if (tag === "input") {
|
|
2357
|
-
const type = (el.type || "text").toLowerCase();
|
|
2358
|
-
if (type === "checkbox") return "checkbox";
|
|
2359
|
-
if (type === "radio") return "radio";
|
|
2360
|
-
if (type === "number") return "number";
|
|
2361
|
-
return "text";
|
|
2362
|
-
}
|
|
2363
|
-
|
|
2364
|
-
if (tag === "select") {
|
|
2365
|
-
return el.multiple ? "select-multiple" : "select";
|
|
2366
|
-
}
|
|
2367
|
-
|
|
2368
|
-
return null;
|
|
2369
|
-
}
|
|
2370
|
-
|
|
2371
|
-
JSswift.rodModel = function (target, rodObj, opts = {}) {
|
|
2372
|
-
if (!rodObj || rodObj.type !== "rod") {
|
|
2373
|
-
throw new Error("[JSswift.rodModel] rodObj deve essere un rod");
|
|
2374
|
-
}
|
|
2375
|
-
|
|
2376
|
-
// Supporta singolo elemento o lista (radio)
|
|
2377
|
-
const elements = Array.isArray(target) || target instanceof NodeList
|
|
2378
|
-
? Array.from(target)
|
|
2379
|
-
: [target];
|
|
2380
|
-
|
|
2381
|
-
const eventName = opts.event ?? "change";
|
|
2382
|
-
const parse = opts.parse ?? (v => v);
|
|
2383
|
-
const format = opts.format ?? (v => v);
|
|
2384
|
-
|
|
2385
|
-
let syncing = false;
|
|
2386
|
-
const disposers = [];
|
|
2387
|
-
|
|
2388
|
-
// === ROD → DOM ===
|
|
2389
|
-
const updateDOM = (value) => {
|
|
2390
|
-
for (const el of elements) {
|
|
2391
|
-
if (!el) continue;
|
|
2392
|
-
const kind = getInputKind(el);
|
|
2393
|
-
|
|
2394
|
-
switch (kind) {
|
|
2395
|
-
case "checkbox":
|
|
2396
|
-
el.checked = !!value;
|
|
2397
|
-
break;
|
|
2398
|
-
|
|
2399
|
-
case "radio":
|
|
2400
|
-
el.checked = el.value == value;
|
|
2401
|
-
break;
|
|
2402
|
-
|
|
2403
|
-
case "select":
|
|
2404
|
-
el.value = value ?? "";
|
|
2405
|
-
break;
|
|
2406
|
-
|
|
2407
|
-
case "select-multiple":
|
|
2408
|
-
const arr = Array.isArray(value) ? value.map(String) : [];
|
|
2409
|
-
for (const opt of el.options) {
|
|
2410
|
-
opt.selected = arr.includes(opt.value);
|
|
2411
|
-
}
|
|
2412
|
-
break;
|
|
2413
|
-
|
|
2414
|
-
case "number":
|
|
2415
|
-
case "text":
|
|
2416
|
-
default:
|
|
2417
|
-
el.value = value ?? "";
|
|
2418
|
-
}
|
|
2419
|
-
}
|
|
2420
|
-
};
|
|
2421
|
-
|
|
2422
|
-
// bind rod → DOM (usa rod.action)
|
|
2423
|
-
|
|
2424
|
-
if (syncing) return;
|
|
2425
|
-
syncing = true;
|
|
2426
|
-
try {
|
|
2427
|
-
updateDOM(format(v));
|
|
2428
|
-
} finally {
|
|
2429
|
-
syncing = false;
|
|
2430
|
-
}
|
|
2431
|
-
}
|
|
2432
|
-
|
|
2433
|
-
|
|
2434
|
-
|
|
2435
|
-
|
|
2436
|
-
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
|
|
2447
|
-
|
|
2448
|
-
|
|
2449
|
-
|
|
2450
|
-
|
|
2451
|
-
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
|
|
2465
|
-
|
|
2466
|
-
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
|
|
2471
|
-
|
|
2472
|
-
|
|
2473
|
-
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
-
|
|
2477
|
-
|
|
2478
|
-
|
|
2479
|
-
|
|
2480
|
-
|
|
2481
|
-
|
|
2482
|
-
|
|
2483
|
-
|
|
2484
|
-
|
|
2485
|
-
|
|
2486
|
-
|
|
2487
|
-
|
|
2488
|
-
|
|
2489
|
-
|
|
2490
|
-
|
|
2491
|
-
|
|
2492
|
-
|
|
2493
|
-
|
|
2494
|
-
|
|
2495
|
-
|
|
2496
|
-
|
|
2497
|
-
|
|
2498
|
-
|
|
2499
|
-
|
|
2342
|
+
// ===============================
|
|
2343
|
+
// Two-way binding: input <-> rod
|
|
2344
|
+
// ===============================
|
|
2345
|
+
// ===============================
|
|
2346
|
+
// Two-way binding avanzato
|
|
2347
|
+
// input / checkbox / radio / select
|
|
2348
|
+
// ===============================
|
|
2349
|
+
|
|
2350
|
+
function getInputKind(el) {
|
|
2351
|
+
if (!el || !el.tagName) return null;
|
|
2352
|
+
|
|
2353
|
+
const tag = el.tagName.toLowerCase();
|
|
2354
|
+
|
|
2355
|
+
if (tag === "input") {
|
|
2356
|
+
const type = (el.type || "text").toLowerCase();
|
|
2357
|
+
if (type === "checkbox") return "checkbox";
|
|
2358
|
+
if (type === "radio") return "radio";
|
|
2359
|
+
if (type === "number") return "number";
|
|
2360
|
+
return "text";
|
|
2361
|
+
}
|
|
2362
|
+
|
|
2363
|
+
if (tag === "select") {
|
|
2364
|
+
return el.multiple ? "select-multiple" : "select";
|
|
2365
|
+
}
|
|
2366
|
+
|
|
2367
|
+
return null;
|
|
2368
|
+
}
|
|
2369
|
+
|
|
2370
|
+
JSswift.rodModel = function (target, rodObj, opts = {}) {
|
|
2371
|
+
if (!rodObj || rodObj.type !== "rod") {
|
|
2372
|
+
throw new Error("[JSswift.rodModel] rodObj deve essere un rod");
|
|
2373
|
+
}
|
|
2374
|
+
|
|
2375
|
+
// Supporta singolo elemento o lista (radio)
|
|
2376
|
+
const elements = Array.isArray(target) || target instanceof NodeList
|
|
2377
|
+
? Array.from(target)
|
|
2378
|
+
: [target];
|
|
2379
|
+
|
|
2380
|
+
const eventName = opts.event ?? "change";
|
|
2381
|
+
const parse = opts.parse ?? (v => v);
|
|
2382
|
+
const format = opts.format ?? (v => v);
|
|
2383
|
+
|
|
2384
|
+
let syncing = false;
|
|
2385
|
+
const disposers = [];
|
|
2386
|
+
|
|
2387
|
+
// === ROD → DOM ===
|
|
2388
|
+
const updateDOM = (value) => {
|
|
2389
|
+
for (const el of elements) {
|
|
2390
|
+
if (!el) continue;
|
|
2391
|
+
const kind = getInputKind(el);
|
|
2392
|
+
|
|
2393
|
+
switch (kind) {
|
|
2394
|
+
case "checkbox":
|
|
2395
|
+
el.checked = !!value;
|
|
2396
|
+
break;
|
|
2397
|
+
|
|
2398
|
+
case "radio":
|
|
2399
|
+
el.checked = el.value == value;
|
|
2400
|
+
break;
|
|
2401
|
+
|
|
2402
|
+
case "select":
|
|
2403
|
+
el.value = value ?? "";
|
|
2404
|
+
break;
|
|
2405
|
+
|
|
2406
|
+
case "select-multiple":
|
|
2407
|
+
const arr = Array.isArray(value) ? value.map(String) : [];
|
|
2408
|
+
for (const opt of el.options) {
|
|
2409
|
+
opt.selected = arr.includes(opt.value);
|
|
2410
|
+
}
|
|
2411
|
+
break;
|
|
2412
|
+
|
|
2413
|
+
case "number":
|
|
2414
|
+
case "text":
|
|
2415
|
+
default:
|
|
2416
|
+
el.value = value ?? "";
|
|
2417
|
+
}
|
|
2418
|
+
}
|
|
2419
|
+
};
|
|
2420
|
+
|
|
2421
|
+
// bind rod → DOM (usa rod.action)
|
|
2422
|
+
const onValue = (v) => {
|
|
2423
|
+
if (syncing) return;
|
|
2424
|
+
syncing = true;
|
|
2425
|
+
try {
|
|
2426
|
+
updateDOM(format(v));
|
|
2427
|
+
} finally {
|
|
2428
|
+
syncing = false;
|
|
2429
|
+
}
|
|
2430
|
+
};
|
|
2431
|
+
rodObj.action(onValue);
|
|
2432
|
+
|
|
2433
|
+
// sync iniziale
|
|
2434
|
+
updateDOM(format(rodObj.value));
|
|
2435
|
+
|
|
2436
|
+
// === DOM → ROD ===
|
|
2437
|
+
for (const el of elements) {
|
|
2438
|
+
if (!el) continue;
|
|
2439
|
+
|
|
2440
|
+
const kind = getInputKind(el);
|
|
2441
|
+
|
|
2442
|
+
const handler = () => {
|
|
2443
|
+
if (syncing) return;
|
|
2444
|
+
|
|
2445
|
+
let next;
|
|
2446
|
+
|
|
2447
|
+
switch (kind) {
|
|
2448
|
+
case "checkbox":
|
|
2449
|
+
next = el.checked;
|
|
2450
|
+
break;
|
|
2451
|
+
|
|
2452
|
+
case "radio":
|
|
2453
|
+
if (!el.checked) return;
|
|
2454
|
+
next = el.value;
|
|
2455
|
+
break;
|
|
2456
|
+
|
|
2457
|
+
case "select":
|
|
2458
|
+
next = el.value;
|
|
2459
|
+
break;
|
|
2460
|
+
|
|
2461
|
+
case "select-multiple":
|
|
2462
|
+
next = Array.from(el.selectedOptions).map(o => o.value);
|
|
2463
|
+
break;
|
|
2464
|
+
|
|
2465
|
+
case "number":
|
|
2466
|
+
next = parse(el.value === "" ? null : Number(el.value));
|
|
2467
|
+
break;
|
|
2468
|
+
|
|
2469
|
+
case "text":
|
|
2470
|
+
default:
|
|
2471
|
+
next = parse(el.value);
|
|
2472
|
+
}
|
|
2473
|
+
|
|
2474
|
+
syncing = true;
|
|
2475
|
+
try {
|
|
2476
|
+
if (rodObj.value !== next) rodObj.value = next;
|
|
2477
|
+
} finally {
|
|
2478
|
+
syncing = false;
|
|
2479
|
+
}
|
|
2480
|
+
};
|
|
2481
|
+
|
|
2482
|
+
el.addEventListener(eventName, handler);
|
|
2483
|
+
disposers.push(() => el.removeEventListener(eventName, handler));
|
|
2484
|
+
}
|
|
2485
|
+
|
|
2486
|
+
// Release both directions without disposing the caller-owned rod.
|
|
2487
|
+
let disposed = false;
|
|
2488
|
+
const dispose = () => {
|
|
2489
|
+
if (disposed) return;
|
|
2490
|
+
disposed = true;
|
|
2491
|
+
disposers.splice(0).forEach(fn => fn());
|
|
2492
|
+
const actionIndex = rodObj._actions?.indexOf(onValue) ?? -1;
|
|
2493
|
+
if (actionIndex >= 0) rodObj._actions.splice(actionIndex, 1);
|
|
2494
|
+
const hookIndex = rodObj._disposeHooks?.indexOf(dispose) ?? -1;
|
|
2495
|
+
if (hookIndex >= 0 && !rodObj._disposed) rodObj._disposeHooks.splice(hookIndex, 1);
|
|
2496
|
+
};
|
|
2497
|
+
if (typeof rodObj.onDispose === "function") rodObj.onDispose(dispose);
|
|
2498
|
+
return dispose;
|
|
2499
|
+
};
|
|
2500
|
+
|
|
2501
|
+
|
|
2502
|
+
// input <-> signal (sugar)
|
|
2503
|
+
JSswift.signalModel = function (inputEl, get, set, opts = {}) {
|
|
2504
|
+
const r = JSswift.rodFromSignal(get, set);
|
|
2505
|
+
const stopBinding = JSswift.rodModel(inputEl, r, opts);
|
|
2506
|
+
return () => {
|
|
2507
|
+
stopBinding();
|
|
2508
|
+
r.dispose();
|
|
2509
|
+
};
|
|
2510
|
+
};
|
|
2500
2511
|
// ===============================
|
|
2501
2512
|
// Lifecycle helpers
|
|
2502
2513
|
// ===============================
|
|
@@ -7731,7 +7742,8 @@ const unitCover = (v, name = 'size') => {
|
|
|
7731
7742
|
|| typeof noteSlot === "function"
|
|
7732
7743
|
|| typeof hintSlot === "function";
|
|
7733
7744
|
if (canReact) {
|
|
7734
|
-
JSswift.reactive.effect(() => { renderMessage(); }, "UI.FormField:message");
|
|
7745
|
+
const stopMessage = JSswift.reactive.effect(() => { renderMessage(); }, "UI.FormField:message");
|
|
7746
|
+
JSswift._registerCleanup(wrap, stopMessage);
|
|
7735
7747
|
} else {
|
|
7736
7748
|
renderMessage();
|
|
7737
7749
|
}
|
|
@@ -7754,6 +7766,16 @@ const unitCover = (v, name = 'size') => {
|
|
|
7754
7766
|
value: props.value
|
|
7755
7767
|
});
|
|
7756
7768
|
|
|
7769
|
+
let autofillDisposed = false;
|
|
7770
|
+
const autofillTimeouts = new Set();
|
|
7771
|
+
const autofillLater = (fn, delay) => {
|
|
7772
|
+
if (autofillDisposed) return;
|
|
7773
|
+
const id = setTimeout(() => {
|
|
7774
|
+
autofillTimeouts.delete(id);
|
|
7775
|
+
if (!autofillDisposed) fn();
|
|
7776
|
+
}, delay);
|
|
7777
|
+
autofillTimeouts.add(id);
|
|
7778
|
+
};
|
|
7757
7779
|
let lastKnownValue = el.value ?? "";
|
|
7758
7780
|
const syncAutofill = () => {
|
|
7759
7781
|
const next = el.value ?? "";
|
|
@@ -7765,7 +7787,7 @@ const unitCover = (v, name = 'size') => {
|
|
|
7765
7787
|
let autofillTimer = null;
|
|
7766
7788
|
let autofillProbe = false;
|
|
7767
7789
|
const scheduleAutofillProbe = () => {
|
|
7768
|
-
if (autofillProbe) return;
|
|
7790
|
+
if (autofillDisposed || autofillProbe) return;
|
|
7769
7791
|
autofillProbe = true;
|
|
7770
7792
|
let count = 0;
|
|
7771
7793
|
const tick = () => {
|
|
@@ -7775,12 +7797,12 @@ const unitCover = (v, name = 'size') => {
|
|
|
7775
7797
|
autofillProbe = false;
|
|
7776
7798
|
return;
|
|
7777
7799
|
}
|
|
7778
|
-
|
|
7800
|
+
autofillLater(tick, 120);
|
|
7779
7801
|
};
|
|
7780
|
-
|
|
7802
|
+
autofillLater(tick, 40);
|
|
7781
7803
|
};
|
|
7782
7804
|
const startAutofillWatch = () => {
|
|
7783
|
-
if (autofillTimer) return;
|
|
7805
|
+
if (autofillDisposed || autofillTimer) return;
|
|
7784
7806
|
autofillTimer = setInterval(() => {
|
|
7785
7807
|
if (document.activeElement !== el) {
|
|
7786
7808
|
clearInterval(autofillTimer);
|
|
@@ -7806,27 +7828,33 @@ const unitCover = (v, name = 'size') => {
|
|
|
7806
7828
|
el.addEventListener("focus", () => {
|
|
7807
7829
|
startAutofillWatch();
|
|
7808
7830
|
scheduleAutofillProbe();
|
|
7809
|
-
|
|
7831
|
+
autofillLater(syncAutofill, 50);
|
|
7810
7832
|
});
|
|
7811
7833
|
el.addEventListener("blur", () => {
|
|
7812
7834
|
stopAutofillWatch();
|
|
7813
|
-
|
|
7835
|
+
autofillLater(syncAutofill, 0);
|
|
7814
7836
|
});
|
|
7815
|
-
|
|
7837
|
+
autofillLater(syncAutofill, 0);
|
|
7816
7838
|
scheduleAutofillProbe();
|
|
7817
7839
|
|
|
7840
|
+
JSswift._registerCleanup(el, () => {
|
|
7841
|
+
autofillDisposed = true;
|
|
7842
|
+
stopAutofillWatch();
|
|
7843
|
+
autofillTimeouts.forEach(clearTimeout);
|
|
7844
|
+
autofillTimeouts.clear();
|
|
7845
|
+
});
|
|
7846
|
+
|
|
7818
7847
|
// model binding
|
|
7819
7848
|
const model = props.model;
|
|
7820
7849
|
if (model) {
|
|
7821
7850
|
// Supporta rod direttamente
|
|
7822
7851
|
if (typeof model === "object" && typeof model._bind === "function") {
|
|
7823
|
-
app.rodModel(el, model);
|
|
7852
|
+
JSswift._registerCleanup(el, app.rodModel(el, model));
|
|
7824
7853
|
}
|
|
7825
7854
|
// Supporta [get,set] signal -> creiamo rodFromSignal
|
|
7826
7855
|
else if (Array.isArray(model) && typeof model[0] === "function" && typeof model[1] === "function") {
|
|
7827
|
-
const
|
|
7828
|
-
|
|
7829
|
-
// NOTE: se vuoi cleanup automatico qui, lo facciamo nel layer component (v2)
|
|
7856
|
+
const stop = JSswift.reactive.untracked(() => app.signalModel(el, model[0], model[1]));
|
|
7857
|
+
JSswift._registerCleanup(el, stop);
|
|
7830
7858
|
}
|
|
7831
7859
|
}
|
|
7832
7860
|
uiRegisterShortcode(el, props, {
|
|
@@ -7843,7 +7871,7 @@ const unitCover = (v, name = 'size') => {
|
|
|
7843
7871
|
if (value == null || value === false) control.removeAttribute(name);
|
|
7844
7872
|
else control.setAttribute(name, String(value));
|
|
7845
7873
|
};
|
|
7846
|
-
JSswift.reactive.effect(() => {
|
|
7874
|
+
const stopAttributes = JSswift.reactive.effect(() => {
|
|
7847
7875
|
const label = uiUnwrap(props.ariaLabel) ?? (typeof props.label === "string" ? props.label : null);
|
|
7848
7876
|
set("aria-label", label);
|
|
7849
7877
|
set("aria-labelledby", uiUnwrap(props.ariaLabelledBy));
|
|
@@ -7858,6 +7886,7 @@ const unitCover = (v, name = 'size') => {
|
|
|
7858
7886
|
set(name.toLowerCase(), uiUnwrap(props[name]));
|
|
7859
7887
|
}
|
|
7860
7888
|
}, "UI.Field:controlAttributes");
|
|
7889
|
+
JSswift._registerCleanup(field, stopAttributes);
|
|
7861
7890
|
}
|
|
7862
7891
|
|
|
7863
7892
|
UI.Input = (props = {}) => {
|
|
@@ -7877,6 +7906,16 @@ const unitCover = (v, name = 'size') => {
|
|
|
7877
7906
|
readOnly: !!props.readonly
|
|
7878
7907
|
});
|
|
7879
7908
|
|
|
7909
|
+
let autofillDisposed = false;
|
|
7910
|
+
const autofillTimeouts = new Set();
|
|
7911
|
+
const autofillLater = (fn, delay) => {
|
|
7912
|
+
if (autofillDisposed) return;
|
|
7913
|
+
const id = setTimeout(() => {
|
|
7914
|
+
autofillTimeouts.delete(id);
|
|
7915
|
+
if (!autofillDisposed) fn();
|
|
7916
|
+
}, delay);
|
|
7917
|
+
autofillTimeouts.add(id);
|
|
7918
|
+
};
|
|
7880
7919
|
let lastKnownValue = input.value ?? "";
|
|
7881
7920
|
const syncAutofill = () => {
|
|
7882
7921
|
const next = input.value ?? "";
|
|
@@ -7888,7 +7927,7 @@ const unitCover = (v, name = 'size') => {
|
|
|
7888
7927
|
let autofillTimer = null;
|
|
7889
7928
|
let autofillProbe = false;
|
|
7890
7929
|
const scheduleAutofillProbe = () => {
|
|
7891
|
-
if (autofillProbe) return;
|
|
7930
|
+
if (autofillDisposed || autofillProbe) return;
|
|
7892
7931
|
autofillProbe = true;
|
|
7893
7932
|
let count = 0;
|
|
7894
7933
|
const tick = () => {
|
|
@@ -7898,12 +7937,12 @@ const unitCover = (v, name = 'size') => {
|
|
|
7898
7937
|
autofillProbe = false;
|
|
7899
7938
|
return;
|
|
7900
7939
|
}
|
|
7901
|
-
|
|
7940
|
+
autofillLater(tick, 120);
|
|
7902
7941
|
};
|
|
7903
|
-
|
|
7942
|
+
autofillLater(tick, 40);
|
|
7904
7943
|
};
|
|
7905
7944
|
const startAutofillWatch = () => {
|
|
7906
|
-
if (autofillTimer) return;
|
|
7945
|
+
if (autofillDisposed || autofillTimer) return;
|
|
7907
7946
|
autofillTimer = setInterval(() => {
|
|
7908
7947
|
if (document.activeElement !== input) {
|
|
7909
7948
|
clearInterval(autofillTimer);
|
|
@@ -7932,12 +7971,12 @@ const unitCover = (v, name = 'size') => {
|
|
|
7932
7971
|
props.onFocus?.(e);
|
|
7933
7972
|
startAutofillWatch();
|
|
7934
7973
|
scheduleAutofillProbe();
|
|
7935
|
-
|
|
7974
|
+
autofillLater(syncAutofill, 50);
|
|
7936
7975
|
});
|
|
7937
7976
|
input.addEventListener("blur", (e) => {
|
|
7938
7977
|
props.onBlur?.(e);
|
|
7939
7978
|
stopAutofillWatch();
|
|
7940
|
-
|
|
7979
|
+
autofillLater(syncAutofill, 0);
|
|
7941
7980
|
});
|
|
7942
7981
|
input.addEventListener("animationstart", (e) => {
|
|
7943
7982
|
if (e.animationName !== "cms-autofill") return;
|
|
@@ -7945,9 +7984,16 @@ const unitCover = (v, name = 'size') => {
|
|
|
7945
7984
|
startAutofillWatch();
|
|
7946
7985
|
scheduleAutofillProbe();
|
|
7947
7986
|
});
|
|
7948
|
-
|
|
7987
|
+
autofillLater(syncAutofill, 0);
|
|
7949
7988
|
scheduleAutofillProbe();
|
|
7950
7989
|
|
|
7990
|
+
JSswift._registerCleanup(input, () => {
|
|
7991
|
+
autofillDisposed = true;
|
|
7992
|
+
stopAutofillWatch();
|
|
7993
|
+
autofillTimeouts.forEach(clearTimeout);
|
|
7994
|
+
autofillTimeouts.clear();
|
|
7995
|
+
});
|
|
7996
|
+
|
|
7951
7997
|
// model binding
|
|
7952
7998
|
const model = props.model;
|
|
7953
7999
|
if (model) {
|
|
@@ -7961,11 +8007,12 @@ const unitCover = (v, name = 'size') => {
|
|
|
7961
8007
|
lastKnownValue = next;
|
|
7962
8008
|
};
|
|
7963
8009
|
|
|
7964
|
-
|
|
8010
|
+
const onValue = (v) => {
|
|
7965
8011
|
if (syncing) return;
|
|
7966
8012
|
syncing = true;
|
|
7967
8013
|
try { updateFromRod(v); } finally { syncing = false; }
|
|
7968
|
-
}
|
|
8014
|
+
};
|
|
8015
|
+
rod.action(onValue);
|
|
7969
8016
|
|
|
7970
8017
|
// Component setup may run inside a dynamic parent render. This initial
|
|
7971
8018
|
// hydration must not make that parent subscribe to the control model.
|
|
@@ -7982,18 +8029,26 @@ const unitCover = (v, name = 'size') => {
|
|
|
7982
8029
|
input.addEventListener("input", onInput);
|
|
7983
8030
|
input.addEventListener("compositionend", onInput);
|
|
7984
8031
|
|
|
7985
|
-
|
|
7986
|
-
|
|
7987
|
-
|
|
7988
|
-
|
|
7989
|
-
|
|
7990
|
-
|
|
8032
|
+
let disposed = false;
|
|
8033
|
+
const stop = () => {
|
|
8034
|
+
if (disposed) return;
|
|
8035
|
+
disposed = true;
|
|
8036
|
+
input.removeEventListener("input", onInput);
|
|
8037
|
+
input.removeEventListener("compositionend", onInput);
|
|
8038
|
+
const actionIndex = rod._actions?.indexOf(onValue) ?? -1;
|
|
8039
|
+
if (actionIndex >= 0) rod._actions.splice(actionIndex, 1);
|
|
8040
|
+
const hookIndex = rod._disposeHooks?.indexOf(stop) ?? -1;
|
|
8041
|
+
if (hookIndex >= 0 && !rod._disposed) rod._disposeHooks.splice(hookIndex, 1);
|
|
8042
|
+
};
|
|
8043
|
+
rod.onDispose?.(stop);
|
|
8044
|
+
JSswift._registerCleanup(input, stop);
|
|
7991
8045
|
};
|
|
7992
8046
|
if (typeof model === "object" && typeof model._bind === "function") {
|
|
7993
8047
|
bindInputRod(model);
|
|
7994
8048
|
} else if (Array.isArray(model) && typeof model[0] === "function" && typeof model[1] === "function") {
|
|
7995
8049
|
const r = JSswift.reactive.untracked(() => JSswift.rodFromSignal(model[0], model[1]));
|
|
7996
8050
|
bindInputRod(r);
|
|
8051
|
+
JSswift._registerCleanup(input, () => r.dispose());
|
|
7997
8052
|
}
|
|
7998
8053
|
}
|
|
7999
8054
|
|
|
@@ -8077,11 +8132,12 @@ const unitCover = (v, name = 'size') => {
|
|
|
8077
8132
|
textarea.value = next;
|
|
8078
8133
|
};
|
|
8079
8134
|
|
|
8080
|
-
|
|
8135
|
+
const onValue = (v) => {
|
|
8081
8136
|
if (syncing) return;
|
|
8082
8137
|
syncing = true;
|
|
8083
8138
|
try { updateFromRod(v); } finally { syncing = false; }
|
|
8084
|
-
}
|
|
8139
|
+
};
|
|
8140
|
+
rod.action(onValue);
|
|
8085
8141
|
|
|
8086
8142
|
JSswift.reactive.untracked(() => updateFromRod(rod.value));
|
|
8087
8143
|
|
|
@@ -8096,18 +8152,26 @@ const unitCover = (v, name = 'size') => {
|
|
|
8096
8152
|
textarea.addEventListener("input", onInput);
|
|
8097
8153
|
textarea.addEventListener("compositionend", onInput);
|
|
8098
8154
|
|
|
8099
|
-
|
|
8100
|
-
|
|
8101
|
-
|
|
8102
|
-
|
|
8103
|
-
|
|
8104
|
-
|
|
8155
|
+
let disposed = false;
|
|
8156
|
+
const stop = () => {
|
|
8157
|
+
if (disposed) return;
|
|
8158
|
+
disposed = true;
|
|
8159
|
+
textarea.removeEventListener("input", onInput);
|
|
8160
|
+
textarea.removeEventListener("compositionend", onInput);
|
|
8161
|
+
const actionIndex = rod._actions?.indexOf(onValue) ?? -1;
|
|
8162
|
+
if (actionIndex >= 0) rod._actions.splice(actionIndex, 1);
|
|
8163
|
+
const hookIndex = rod._disposeHooks?.indexOf(stop) ?? -1;
|
|
8164
|
+
if (hookIndex >= 0 && !rod._disposed) rod._disposeHooks.splice(hookIndex, 1);
|
|
8165
|
+
};
|
|
8166
|
+
rod.onDispose?.(stop);
|
|
8167
|
+
JSswift._registerCleanup(textarea, stop);
|
|
8105
8168
|
};
|
|
8106
8169
|
if (typeof model === "object" && typeof model._bind === "function") {
|
|
8107
8170
|
bindTextareaRod(model);
|
|
8108
8171
|
} else if (Array.isArray(model) && typeof model[0] === "function" && typeof model[1] === "function") {
|
|
8109
8172
|
const r = JSswift.reactive.untracked(() => JSswift.rodFromSignal(model[0], model[1]));
|
|
8110
8173
|
bindTextareaRod(r);
|
|
8174
|
+
JSswift._registerCleanup(textarea, () => r.dispose());
|
|
8111
8175
|
}
|
|
8112
8176
|
}
|
|
8113
8177
|
|
|
@@ -8143,7 +8207,22 @@ const unitCover = (v, name = 'size') => {
|
|
|
8143
8207
|
};
|
|
8144
8208
|
|
|
8145
8209
|
|
|
8210
|
+
// Component-owned subscriptions; shared models remain caller-owned.
|
|
8211
|
+
function uiSubscriptionScope() {
|
|
8212
|
+
const stops = [];
|
|
8213
|
+
return {
|
|
8214
|
+
effect: (fn, name) => { const stop = JSswift.reactive.effect(fn, name); stops.push(stop); return stop; },
|
|
8215
|
+
action: (rod, fn) => {
|
|
8216
|
+
rod.action(fn);
|
|
8217
|
+
stops.push(() => { const i = rod._actions?.indexOf(fn) ?? -1; if (i >= 0) rod._actions.splice(i, 1); });
|
|
8218
|
+
},
|
|
8219
|
+
dispose: () => stops.splice(0).forEach(stop => stop())
|
|
8220
|
+
};
|
|
8221
|
+
}
|
|
8222
|
+
|
|
8146
8223
|
UI.Search = (props = {}) => {
|
|
8224
|
+
const scope = uiSubscriptionScope();
|
|
8225
|
+
let disposed = false;
|
|
8147
8226
|
const slots = props.slots || {};
|
|
8148
8227
|
const isBoundValue = (v) => !!v && ((typeof v === "object" && typeof v._bind === "function") || uiIsSignal(v));
|
|
8149
8228
|
const valueBinding = isBoundValue(props.model)
|
|
@@ -8256,6 +8335,7 @@ const unitCover = (v, name = 'size') => {
|
|
|
8256
8335
|
setActive(-1);
|
|
8257
8336
|
};
|
|
8258
8337
|
const runSearch = async (query, options = {}) => {
|
|
8338
|
+
if (disposed) return;
|
|
8259
8339
|
const q = query == null ? "" : String(query).trim();
|
|
8260
8340
|
const id = ++requestId;
|
|
8261
8341
|
const endpoint = uiUnwrap(props.endpoint ?? props.url);
|
|
@@ -8310,7 +8390,7 @@ const unitCover = (v, name = 'size') => {
|
|
|
8310
8390
|
} else {
|
|
8311
8391
|
raw = [];
|
|
8312
8392
|
}
|
|
8313
|
-
if (id !== requestId) return;
|
|
8393
|
+
if (disposed || id !== requestId) return;
|
|
8314
8394
|
const mapped = typeof props.mapResponse === "function" ? props.mapResponse(raw, q) : normalizeResults(raw);
|
|
8315
8395
|
const results = normalizeResults(mapped);
|
|
8316
8396
|
if (cacheEnabled) cache.set(cacheKey, results);
|
|
@@ -8319,13 +8399,13 @@ const unitCover = (v, name = 'size') => {
|
|
|
8319
8399
|
props.onResults?.(results, q);
|
|
8320
8400
|
} catch (error) {
|
|
8321
8401
|
if (error?.name === "AbortError") return;
|
|
8322
|
-
if (id !== requestId) return;
|
|
8402
|
+
if (disposed || id !== requestId) return;
|
|
8323
8403
|
setError(error);
|
|
8324
8404
|
setResults([]);
|
|
8325
8405
|
setActive(-1);
|
|
8326
8406
|
props.onError?.(error, q);
|
|
8327
8407
|
} finally {
|
|
8328
|
-
if (id === requestId) {
|
|
8408
|
+
if (!disposed && id === requestId) {
|
|
8329
8409
|
setLoading(false);
|
|
8330
8410
|
activeController = null;
|
|
8331
8411
|
scheduleMenuPosition();
|
|
@@ -8333,6 +8413,7 @@ const unitCover = (v, name = 'size') => {
|
|
|
8333
8413
|
}
|
|
8334
8414
|
};
|
|
8335
8415
|
const scheduleSearch = (query, options = {}) => {
|
|
8416
|
+
if (disposed) return;
|
|
8336
8417
|
if (searchTimer) clearTimeout(searchTimer);
|
|
8337
8418
|
const delay = options.immediate ? 0 : debounceMs();
|
|
8338
8419
|
searchTimer = setTimeout(() => {
|
|
@@ -8493,18 +8574,18 @@ const unitCover = (v, name = 'size') => {
|
|
|
8493
8574
|
|
|
8494
8575
|
if (valueBinding) {
|
|
8495
8576
|
if (typeof valueBinding === "object" && typeof valueBinding._bind === "function") {
|
|
8496
|
-
|
|
8577
|
+
scope.action(valueBinding, (v) => setQuery(v, { fromModel: true }));
|
|
8497
8578
|
modelSet = (v) => {
|
|
8498
8579
|
if (valueBinding.value !== v) valueBinding.value = v;
|
|
8499
8580
|
};
|
|
8500
8581
|
} else if (Array.isArray(valueBinding) && typeof valueBinding[0] === "function" && typeof valueBinding[1] === "function") {
|
|
8501
8582
|
const get = valueBinding[0];
|
|
8502
8583
|
const set = valueBinding[1];
|
|
8503
|
-
|
|
8584
|
+
scope.effect(() => { setQuery(get(), { fromModel: true }); }, "UI.Search:model");
|
|
8504
8585
|
modelSet = (v) => set(v);
|
|
8505
8586
|
}
|
|
8506
8587
|
} else if (uiIsReactive(props.value)) {
|
|
8507
|
-
|
|
8588
|
+
scope.effect(() => { setQuery(uiUnwrap(props.value), { fromModel: true }); }, "UI.Search:value");
|
|
8508
8589
|
}
|
|
8509
8590
|
|
|
8510
8591
|
input.addEventListener("input", (event) => {
|
|
@@ -8578,7 +8659,7 @@ const unitCover = (v, name = 'size') => {
|
|
|
8578
8659
|
};
|
|
8579
8660
|
document.addEventListener("click", onDocClick, true);
|
|
8580
8661
|
|
|
8581
|
-
|
|
8662
|
+
scope.effect(() => {
|
|
8582
8663
|
const openNow = getOpen();
|
|
8583
8664
|
root.classList.toggle("open", openNow);
|
|
8584
8665
|
input.setAttribute("aria-expanded", openNow ? "true" : "false");
|
|
@@ -8586,7 +8667,7 @@ const unitCover = (v, name = 'size') => {
|
|
|
8586
8667
|
else unmountMenuPortal();
|
|
8587
8668
|
}, "UI.Search:open");
|
|
8588
8669
|
|
|
8589
|
-
|
|
8670
|
+
scope.effect(() => {
|
|
8590
8671
|
const d = isDisabled();
|
|
8591
8672
|
const r = isReadonly();
|
|
8592
8673
|
root.classList.toggle("disabled", d);
|
|
@@ -8595,7 +8676,7 @@ const unitCover = (v, name = 'size') => {
|
|
|
8595
8676
|
input.readOnly = r;
|
|
8596
8677
|
}, "UI.Search:disabled");
|
|
8597
8678
|
|
|
8598
|
-
|
|
8679
|
+
scope.effect(() => {
|
|
8599
8680
|
const loading = getLoading();
|
|
8600
8681
|
const error = getError();
|
|
8601
8682
|
const results = getResults();
|
|
@@ -8649,7 +8730,7 @@ const unitCover = (v, name = 'size') => {
|
|
|
8649
8730
|
scheduleMenuPosition();
|
|
8650
8731
|
}, "UI.Search:render");
|
|
8651
8732
|
|
|
8652
|
-
|
|
8733
|
+
scope.effect(() => {
|
|
8653
8734
|
const active = getActive();
|
|
8654
8735
|
if (active === lastActive) return;
|
|
8655
8736
|
if (resultNodes[lastActive]) {
|
|
@@ -8692,10 +8773,12 @@ const unitCover = (v, name = 'size') => {
|
|
|
8692
8773
|
field._searchNow = () => runSearch(getQuery(), { immediate: true, open: true });
|
|
8693
8774
|
field._getResults = getResults;
|
|
8694
8775
|
field._setValue = (value) => setQuery(value, {});
|
|
8695
|
-
let disposed = false;
|
|
8696
8776
|
field._dispose = () => {
|
|
8697
8777
|
if (disposed) return;
|
|
8698
8778
|
disposed = true;
|
|
8779
|
+
scope.dispose();
|
|
8780
|
+
requestId++;
|
|
8781
|
+
cache.clear();
|
|
8699
8782
|
document.removeEventListener("click", onDocClick, true);
|
|
8700
8783
|
if (searchTimer) clearTimeout(searchTimer);
|
|
8701
8784
|
if (activeController) activeController.abort();
|
|
@@ -8716,6 +8799,8 @@ const unitCover = (v, name = 'size') => {
|
|
|
8716
8799
|
|
|
8717
8800
|
|
|
8718
8801
|
UI.Select = (props = {}) => {
|
|
8802
|
+
const scope = uiSubscriptionScope();
|
|
8803
|
+
let disposed = false;
|
|
8719
8804
|
const fieldProps = {
|
|
8720
8805
|
...props,
|
|
8721
8806
|
label: props.label ?? props.placeholder
|
|
@@ -8758,7 +8843,7 @@ const unitCover = (v, name = 'size') => {
|
|
|
8758
8843
|
if (valueBinding) {
|
|
8759
8844
|
if (typeof valueBinding === "object" && typeof valueBinding._bind === "function") {
|
|
8760
8845
|
JSswift.reactive.untracked(() => setValue(normalizeValue(valueBinding.value)));
|
|
8761
|
-
|
|
8846
|
+
scope.action(valueBinding, (v) => setValue(normalizeValue(v)));
|
|
8762
8847
|
modelSet = (v) => {
|
|
8763
8848
|
const next = normalizeValue(v);
|
|
8764
8849
|
if (valueBinding.value !== next) valueBinding.value = next;
|
|
@@ -8767,7 +8852,7 @@ const unitCover = (v, name = 'size') => {
|
|
|
8767
8852
|
const get = valueBinding[0];
|
|
8768
8853
|
const set = valueBinding[1];
|
|
8769
8854
|
JSswift.reactive.untracked(() => {
|
|
8770
|
-
|
|
8855
|
+
scope.effect(() => { setValue(normalizeValue(get())); }, "UI.Select:model");
|
|
8771
8856
|
});
|
|
8772
8857
|
modelSet = (v) => set(normalizeValue(v));
|
|
8773
8858
|
}
|
|
@@ -8834,6 +8919,7 @@ const unitCover = (v, name = 'size') => {
|
|
|
8834
8919
|
|
|
8835
8920
|
// supports async options
|
|
8836
8921
|
async function loadOptions() {
|
|
8922
|
+
if (disposed) return;
|
|
8837
8923
|
const src = props.options;
|
|
8838
8924
|
let raw;
|
|
8839
8925
|
|
|
@@ -8847,12 +8933,13 @@ const unitCover = (v, name = 'size') => {
|
|
|
8847
8933
|
} else {
|
|
8848
8934
|
raw = v;
|
|
8849
8935
|
}
|
|
8850
|
-
buildLists(raw);
|
|
8936
|
+
if (!disposed) buildLists(raw);
|
|
8851
8937
|
} catch (e) {
|
|
8938
|
+
if (disposed) return;
|
|
8852
8939
|
console.error("[UI.Select] options load error:", e);
|
|
8853
8940
|
buildLists([]);
|
|
8854
8941
|
} finally {
|
|
8855
|
-
setLoading(false);
|
|
8942
|
+
if (!disposed) setLoading(false);
|
|
8856
8943
|
}
|
|
8857
8944
|
}
|
|
8858
8945
|
|
|
@@ -8892,11 +8979,12 @@ const unitCover = (v, name = 'size') => {
|
|
|
8892
8979
|
}
|
|
8893
8980
|
|
|
8894
8981
|
async function open() {
|
|
8895
|
-
if (isDisabled()) return;
|
|
8982
|
+
if (disposed || isDisabled()) return;
|
|
8896
8983
|
setOpen(true);
|
|
8897
8984
|
|
|
8898
8985
|
// load options (sync/async) on open
|
|
8899
8986
|
await loadOptions();
|
|
8987
|
+
if (disposed) return;
|
|
8900
8988
|
|
|
8901
8989
|
// set active to current selected, otherwise first enabled
|
|
8902
8990
|
const current = getValue();
|
|
@@ -8934,7 +9022,7 @@ const unitCover = (v, name = 'size') => {
|
|
|
8934
9022
|
const valueNode = _.div({
|
|
8935
9023
|
class: uiClass(["cms-select-value", isMulti ? "cms-select-value-multi" : ""])
|
|
8936
9024
|
});
|
|
8937
|
-
|
|
9025
|
+
scope.effect(() => {
|
|
8938
9026
|
const flat = getFlat();
|
|
8939
9027
|
valueNode.innerHTML = "";
|
|
8940
9028
|
if (isMulti) {
|
|
@@ -8981,7 +9069,7 @@ const unitCover = (v, name = 'size') => {
|
|
|
8981
9069
|
}, "UI.Select:value");
|
|
8982
9070
|
|
|
8983
9071
|
const arrowWrap = _.div({ class: "cms-select-arrow" });
|
|
8984
|
-
|
|
9072
|
+
scope.effect(() => {
|
|
8985
9073
|
const arrowNode = JSswift.ui.renderSlot(slots, "arrow", { open: getOpen() }, UI.Icon("#chevron-down"));
|
|
8986
9074
|
arrowWrap.innerHTML = "";
|
|
8987
9075
|
renderSlotToArray(null, "default", {}, arrowNode).forEach(n => arrowWrap.appendChild(n));
|
|
@@ -9023,7 +9111,7 @@ const unitCover = (v, name = 'size') => {
|
|
|
9023
9111
|
if (filterInput) filterInput.value = getFilter() || "";
|
|
9024
9112
|
};
|
|
9025
9113
|
JSswift.reactive.untracked(renderFilterSlot);
|
|
9026
|
-
|
|
9114
|
+
scope.effect(() => {
|
|
9027
9115
|
const next = getFilter() || "";
|
|
9028
9116
|
if (filterInput && "value" in filterInput && filterInput.value !== next) {
|
|
9029
9117
|
filterInput.value = next;
|
|
@@ -9032,7 +9120,7 @@ const unitCover = (v, name = 'size') => {
|
|
|
9032
9120
|
|
|
9033
9121
|
if (typeof filterable === "boolean") filterable ? filterWrap.classList.remove("cms-d-none") : filterWrap.classList.add("cms-d-none");
|
|
9034
9122
|
else if (filterable?.action) {
|
|
9035
|
-
|
|
9123
|
+
scope.action(filterable, (v) => {
|
|
9036
9124
|
v ? filterWrap.classList.remove("cms-d-none") : filterWrap.classList.add("cms-d-none");
|
|
9037
9125
|
});
|
|
9038
9126
|
filterable.value ? filterWrap.classList.remove("cms-d-none") : filterWrap.classList.add("cms-d-none");;
|
|
@@ -9133,7 +9221,7 @@ const unitCover = (v, name = 'size') => {
|
|
|
9133
9221
|
root.appendChild(menu);
|
|
9134
9222
|
|
|
9135
9223
|
// open class + aria
|
|
9136
|
-
|
|
9224
|
+
scope.effect(() => {
|
|
9137
9225
|
const o = getOpen();
|
|
9138
9226
|
root.classList.toggle("open", o);
|
|
9139
9227
|
root.setAttribute("aria-expanded", o ? "true" : "false");
|
|
@@ -9141,7 +9229,7 @@ const unitCover = (v, name = 'size') => {
|
|
|
9141
9229
|
else unmountMenuPortal();
|
|
9142
9230
|
}, "UI.Select:open");
|
|
9143
9231
|
|
|
9144
|
-
|
|
9232
|
+
scope.effect(() => {
|
|
9145
9233
|
const d = isDisabled();
|
|
9146
9234
|
root.classList.toggle("disabled", d);
|
|
9147
9235
|
root.tabIndex = d ? -1 : 0;
|
|
@@ -9163,7 +9251,7 @@ const unitCover = (v, name = 'size') => {
|
|
|
9163
9251
|
if (active >= 0) optionNodes[active]?.scrollIntoView({ block: "nearest" });
|
|
9164
9252
|
}
|
|
9165
9253
|
|
|
9166
|
-
|
|
9254
|
+
scope.effect(() => {
|
|
9167
9255
|
const display = getList();
|
|
9168
9256
|
const flat = getFlat();
|
|
9169
9257
|
const filter = (getFilter() || "").toLowerCase().trim();
|
|
@@ -9260,7 +9348,7 @@ const unitCover = (v, name = 'size') => {
|
|
|
9260
9348
|
if (getOpen()) scheduleMenuPosition();
|
|
9261
9349
|
}, "UI.Select:render");
|
|
9262
9350
|
|
|
9263
|
-
|
|
9351
|
+
scope.effect(() => {
|
|
9264
9352
|
paintActive();
|
|
9265
9353
|
}, "UI.Select:paintActive");
|
|
9266
9354
|
|
|
@@ -9374,10 +9462,10 @@ const unitCover = (v, name = 'size') => {
|
|
|
9374
9462
|
root.addEventListener("keydown", onKeyDown);
|
|
9375
9463
|
filterWrap.addEventListener("keydown", onKeyDown);
|
|
9376
9464
|
|
|
9377
|
-
let disposed = false;
|
|
9378
9465
|
root._dispose = () => {
|
|
9379
9466
|
if (disposed) return;
|
|
9380
9467
|
disposed = true;
|
|
9468
|
+
scope.dispose();
|
|
9381
9469
|
document.removeEventListener("click", onDocClick, true);
|
|
9382
9470
|
root.removeEventListener("keydown", onKeyDown);
|
|
9383
9471
|
filterWrap.removeEventListener("keydown", onKeyDown);
|
|
@@ -9417,7 +9505,7 @@ const unitCover = (v, name = 'size') => {
|
|
|
9417
9505
|
});
|
|
9418
9506
|
|
|
9419
9507
|
// refresh has-value when value changes
|
|
9420
|
-
|
|
9508
|
+
scope.effect(() => { field._refresh?.(); }, "UI.Select:fieldRefresh");
|
|
9421
9509
|
|
|
9422
9510
|
uiConfigureFieldControl(field, root, props);
|
|
9423
9511
|
field._select = root;
|
|
@@ -10000,7 +10088,10 @@ const unitCover = (v, name = 'size') => {
|
|
|
10000
10088
|
return {
|
|
10001
10089
|
get: () => model.value,
|
|
10002
10090
|
set: (v) => { if (model.value !== v) model.value = v; },
|
|
10003
|
-
watch: (fn) => {
|
|
10091
|
+
watch: (fn) => {
|
|
10092
|
+
model.action?.(fn);
|
|
10093
|
+
return () => { const i = model._actions?.indexOf(fn) ?? -1; if (i >= 0) model._actions.splice(i, 1); };
|
|
10094
|
+
}
|
|
10004
10095
|
};
|
|
10005
10096
|
}
|
|
10006
10097
|
if (Array.isArray(model) && typeof model[0] === "function" && typeof model[1] === "function") {
|
|
@@ -10178,7 +10269,7 @@ const unitCover = (v, name = 'size') => {
|
|
|
10178
10269
|
if (getOpen() !== next) syncOpen(next);
|
|
10179
10270
|
controlledDrawer?.set(next);
|
|
10180
10271
|
};
|
|
10181
|
-
controlledDrawer?.watch?.(() => {
|
|
10272
|
+
const stopDrawerWatch = controlledDrawer?.watch?.(() => {
|
|
10182
10273
|
const next = !!controlledDrawer.get();
|
|
10183
10274
|
if (getOpen() !== next) syncOpen(next);
|
|
10184
10275
|
});
|
|
@@ -10193,7 +10284,7 @@ const unitCover = (v, name = 'size') => {
|
|
|
10193
10284
|
if (getNavOpen() !== next) syncNavOpen(next);
|
|
10194
10285
|
controlledNav?.set(next);
|
|
10195
10286
|
};
|
|
10196
|
-
controlledNav?.watch?.(() => {
|
|
10287
|
+
const stopNavWatch = controlledNav?.watch?.(() => {
|
|
10197
10288
|
const next = !!controlledNav.get();
|
|
10198
10289
|
if (getNavOpen() !== next) syncNavOpen(next);
|
|
10199
10290
|
});
|
|
@@ -10281,6 +10372,8 @@ const unitCover = (v, name = 'size') => {
|
|
|
10281
10372
|
JSswift.uiApplyResponsiveProps(p, props, JSswift.uiResponsiveStyleRules);
|
|
10282
10373
|
|
|
10283
10374
|
const root = _.div(p);
|
|
10375
|
+
if (stopDrawerWatch) JSswift._registerCleanup(root, stopDrawerWatch);
|
|
10376
|
+
if (stopNavWatch) JSswift._registerCleanup(root, stopNavWatch);
|
|
10284
10377
|
const tagPage = uiUnwrap(resolveLayoutProp("tagPage")) === true;
|
|
10285
10378
|
const tags = tagPage
|
|
10286
10379
|
? { header: "header", aside: "aside", nav: "nav", page: "main", footer: "footer" }
|
|
@@ -21183,8 +21276,9 @@ transition: width 200ms ease;
|
|
|
21183
21276
|
};
|
|
21184
21277
|
|
|
21185
21278
|
let searchInput = null;
|
|
21279
|
+
let stopSearchWatch;
|
|
21186
21280
|
if (searchModel) {
|
|
21187
|
-
searchModel.watch((value) => {
|
|
21281
|
+
stopSearchWatch = searchModel.watch((value) => {
|
|
21188
21282
|
const next = String(value ?? "");
|
|
21189
21283
|
setQueryState(next);
|
|
21190
21284
|
if (searchInput && searchInput.value !== next) searchInput.value = next;
|
|
@@ -21199,7 +21293,7 @@ transition: width 200ms ease;
|
|
|
21199
21293
|
"actions", "actionsLabel", "emptyText", "loadingText", "onRowClick", "onRowDblClick",
|
|
21200
21294
|
"tableClass", "tableStyle", "cardClass", "dense", "striped", "hover", "stickyHeader",
|
|
21201
21295
|
"toolbar", "toolbarStart", "toolbarEnd", "caption", "footer", "status", "rowClass", "rowAttrs",
|
|
21202
|
-
"minTableWidth", "hideHeader", "hideFooter", "slots", "body"
|
|
21296
|
+
"minTableWidth", "hideHeader", "hideFooter", "hideStatus", "slots", "body"
|
|
21203
21297
|
]);
|
|
21204
21298
|
wrapProps.class = uiClass(["cms-table-card", props.class, props.cardClass]);
|
|
21205
21299
|
if (props.dense != null) wrapProps.dense = props.dense;
|
|
@@ -21262,7 +21356,7 @@ transition: width 200ms ease;
|
|
|
21262
21356
|
statusSort,
|
|
21263
21357
|
...statusExtraNodes
|
|
21264
21358
|
);
|
|
21265
|
-
shell.appendChild(status);
|
|
21359
|
+
if (props.hideStatus !== true) shell.appendChild(status);
|
|
21266
21360
|
|
|
21267
21361
|
const captionNodes = renderSlotToArray(slots, "caption", {}, props.caption);
|
|
21268
21362
|
if (captionNodes.length) shell.appendChild(_.div({ class: "cms-table-caption" }, ...captionNodes));
|
|
@@ -21394,7 +21488,7 @@ transition: width 200ms ease;
|
|
|
21394
21488
|
);
|
|
21395
21489
|
};
|
|
21396
21490
|
|
|
21397
|
-
app.reactive.effect(() => {
|
|
21491
|
+
const stopRender = app.reactive.effect(() => {
|
|
21398
21492
|
const rows = tableToArray(props.rows);
|
|
21399
21493
|
const query = String(getQuery() || "").trim();
|
|
21400
21494
|
const currentSort = getSort();
|
|
@@ -21568,7 +21662,10 @@ transition: width 200ms ease;
|
|
|
21568
21662
|
}, "UI.Table:render");
|
|
21569
21663
|
|
|
21570
21664
|
wrapProps.body = shell;
|
|
21571
|
-
|
|
21665
|
+
const card = UI.Card(wrapProps);
|
|
21666
|
+
JSswift._registerCleanup(card, stopRender);
|
|
21667
|
+
if (stopSearchWatch) JSswift._registerCleanup(card, stopSearchWatch);
|
|
21668
|
+
return card;
|
|
21572
21669
|
};
|
|
21573
21670
|
UI.Menu = (...args) => {
|
|
21574
21671
|
const { props, children } = JSswift.uiNormalizeArgs(args);
|