@jsswift/ui 1.0.52 → 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/dist/min-ui.js +3 -3
- package/dist/ui.js +154 -68
- package/dist/ui.min.js +3 -3
- package/package.json +2 -2
- package/src/10-primitives-layout.js +147 -65
- package/src/80-data.js +7 -3
package/dist/ui.js
CHANGED
|
@@ -2446,7 +2446,8 @@ const unitCover = (v, name = 'size') => {
|
|
|
2446
2446
|
|| typeof noteSlot === "function"
|
|
2447
2447
|
|| typeof hintSlot === "function";
|
|
2448
2448
|
if (canReact) {
|
|
2449
|
-
JSswift.reactive.effect(() => { renderMessage(); }, "UI.FormField:message");
|
|
2449
|
+
const stopMessage = JSswift.reactive.effect(() => { renderMessage(); }, "UI.FormField:message");
|
|
2450
|
+
JSswift._registerCleanup(wrap, stopMessage);
|
|
2450
2451
|
} else {
|
|
2451
2452
|
renderMessage();
|
|
2452
2453
|
}
|
|
@@ -2469,6 +2470,16 @@ const unitCover = (v, name = 'size') => {
|
|
|
2469
2470
|
value: props.value
|
|
2470
2471
|
});
|
|
2471
2472
|
|
|
2473
|
+
let autofillDisposed = false;
|
|
2474
|
+
const autofillTimeouts = new Set();
|
|
2475
|
+
const autofillLater = (fn, delay) => {
|
|
2476
|
+
if (autofillDisposed) return;
|
|
2477
|
+
const id = setTimeout(() => {
|
|
2478
|
+
autofillTimeouts.delete(id);
|
|
2479
|
+
if (!autofillDisposed) fn();
|
|
2480
|
+
}, delay);
|
|
2481
|
+
autofillTimeouts.add(id);
|
|
2482
|
+
};
|
|
2472
2483
|
let lastKnownValue = el.value ?? "";
|
|
2473
2484
|
const syncAutofill = () => {
|
|
2474
2485
|
const next = el.value ?? "";
|
|
@@ -2480,7 +2491,7 @@ const unitCover = (v, name = 'size') => {
|
|
|
2480
2491
|
let autofillTimer = null;
|
|
2481
2492
|
let autofillProbe = false;
|
|
2482
2493
|
const scheduleAutofillProbe = () => {
|
|
2483
|
-
if (autofillProbe) return;
|
|
2494
|
+
if (autofillDisposed || autofillProbe) return;
|
|
2484
2495
|
autofillProbe = true;
|
|
2485
2496
|
let count = 0;
|
|
2486
2497
|
const tick = () => {
|
|
@@ -2490,12 +2501,12 @@ const unitCover = (v, name = 'size') => {
|
|
|
2490
2501
|
autofillProbe = false;
|
|
2491
2502
|
return;
|
|
2492
2503
|
}
|
|
2493
|
-
|
|
2504
|
+
autofillLater(tick, 120);
|
|
2494
2505
|
};
|
|
2495
|
-
|
|
2506
|
+
autofillLater(tick, 40);
|
|
2496
2507
|
};
|
|
2497
2508
|
const startAutofillWatch = () => {
|
|
2498
|
-
if (autofillTimer) return;
|
|
2509
|
+
if (autofillDisposed || autofillTimer) return;
|
|
2499
2510
|
autofillTimer = setInterval(() => {
|
|
2500
2511
|
if (document.activeElement !== el) {
|
|
2501
2512
|
clearInterval(autofillTimer);
|
|
@@ -2521,27 +2532,33 @@ const unitCover = (v, name = 'size') => {
|
|
|
2521
2532
|
el.addEventListener("focus", () => {
|
|
2522
2533
|
startAutofillWatch();
|
|
2523
2534
|
scheduleAutofillProbe();
|
|
2524
|
-
|
|
2535
|
+
autofillLater(syncAutofill, 50);
|
|
2525
2536
|
});
|
|
2526
2537
|
el.addEventListener("blur", () => {
|
|
2527
2538
|
stopAutofillWatch();
|
|
2528
|
-
|
|
2539
|
+
autofillLater(syncAutofill, 0);
|
|
2529
2540
|
});
|
|
2530
|
-
|
|
2541
|
+
autofillLater(syncAutofill, 0);
|
|
2531
2542
|
scheduleAutofillProbe();
|
|
2532
2543
|
|
|
2544
|
+
JSswift._registerCleanup(el, () => {
|
|
2545
|
+
autofillDisposed = true;
|
|
2546
|
+
stopAutofillWatch();
|
|
2547
|
+
autofillTimeouts.forEach(clearTimeout);
|
|
2548
|
+
autofillTimeouts.clear();
|
|
2549
|
+
});
|
|
2550
|
+
|
|
2533
2551
|
// model binding
|
|
2534
2552
|
const model = props.model;
|
|
2535
2553
|
if (model) {
|
|
2536
2554
|
// Supporta rod direttamente
|
|
2537
2555
|
if (typeof model === "object" && typeof model._bind === "function") {
|
|
2538
|
-
app.rodModel(el, model);
|
|
2556
|
+
JSswift._registerCleanup(el, app.rodModel(el, model));
|
|
2539
2557
|
}
|
|
2540
2558
|
// Supporta [get,set] signal -> creiamo rodFromSignal
|
|
2541
2559
|
else if (Array.isArray(model) && typeof model[0] === "function" && typeof model[1] === "function") {
|
|
2542
|
-
const
|
|
2543
|
-
|
|
2544
|
-
// NOTE: se vuoi cleanup automatico qui, lo facciamo nel layer component (v2)
|
|
2560
|
+
const stop = JSswift.reactive.untracked(() => app.signalModel(el, model[0], model[1]));
|
|
2561
|
+
JSswift._registerCleanup(el, stop);
|
|
2545
2562
|
}
|
|
2546
2563
|
}
|
|
2547
2564
|
uiRegisterShortcode(el, props, {
|
|
@@ -2558,7 +2575,7 @@ const unitCover = (v, name = 'size') => {
|
|
|
2558
2575
|
if (value == null || value === false) control.removeAttribute(name);
|
|
2559
2576
|
else control.setAttribute(name, String(value));
|
|
2560
2577
|
};
|
|
2561
|
-
JSswift.reactive.effect(() => {
|
|
2578
|
+
const stopAttributes = JSswift.reactive.effect(() => {
|
|
2562
2579
|
const label = uiUnwrap(props.ariaLabel) ?? (typeof props.label === "string" ? props.label : null);
|
|
2563
2580
|
set("aria-label", label);
|
|
2564
2581
|
set("aria-labelledby", uiUnwrap(props.ariaLabelledBy));
|
|
@@ -2573,6 +2590,7 @@ const unitCover = (v, name = 'size') => {
|
|
|
2573
2590
|
set(name.toLowerCase(), uiUnwrap(props[name]));
|
|
2574
2591
|
}
|
|
2575
2592
|
}, "UI.Field:controlAttributes");
|
|
2593
|
+
JSswift._registerCleanup(field, stopAttributes);
|
|
2576
2594
|
}
|
|
2577
2595
|
|
|
2578
2596
|
UI.Input = (props = {}) => {
|
|
@@ -2592,6 +2610,16 @@ const unitCover = (v, name = 'size') => {
|
|
|
2592
2610
|
readOnly: !!props.readonly
|
|
2593
2611
|
});
|
|
2594
2612
|
|
|
2613
|
+
let autofillDisposed = false;
|
|
2614
|
+
const autofillTimeouts = new Set();
|
|
2615
|
+
const autofillLater = (fn, delay) => {
|
|
2616
|
+
if (autofillDisposed) return;
|
|
2617
|
+
const id = setTimeout(() => {
|
|
2618
|
+
autofillTimeouts.delete(id);
|
|
2619
|
+
if (!autofillDisposed) fn();
|
|
2620
|
+
}, delay);
|
|
2621
|
+
autofillTimeouts.add(id);
|
|
2622
|
+
};
|
|
2595
2623
|
let lastKnownValue = input.value ?? "";
|
|
2596
2624
|
const syncAutofill = () => {
|
|
2597
2625
|
const next = input.value ?? "";
|
|
@@ -2603,7 +2631,7 @@ const unitCover = (v, name = 'size') => {
|
|
|
2603
2631
|
let autofillTimer = null;
|
|
2604
2632
|
let autofillProbe = false;
|
|
2605
2633
|
const scheduleAutofillProbe = () => {
|
|
2606
|
-
if (autofillProbe) return;
|
|
2634
|
+
if (autofillDisposed || autofillProbe) return;
|
|
2607
2635
|
autofillProbe = true;
|
|
2608
2636
|
let count = 0;
|
|
2609
2637
|
const tick = () => {
|
|
@@ -2613,12 +2641,12 @@ const unitCover = (v, name = 'size') => {
|
|
|
2613
2641
|
autofillProbe = false;
|
|
2614
2642
|
return;
|
|
2615
2643
|
}
|
|
2616
|
-
|
|
2644
|
+
autofillLater(tick, 120);
|
|
2617
2645
|
};
|
|
2618
|
-
|
|
2646
|
+
autofillLater(tick, 40);
|
|
2619
2647
|
};
|
|
2620
2648
|
const startAutofillWatch = () => {
|
|
2621
|
-
if (autofillTimer) return;
|
|
2649
|
+
if (autofillDisposed || autofillTimer) return;
|
|
2622
2650
|
autofillTimer = setInterval(() => {
|
|
2623
2651
|
if (document.activeElement !== input) {
|
|
2624
2652
|
clearInterval(autofillTimer);
|
|
@@ -2647,12 +2675,12 @@ const unitCover = (v, name = 'size') => {
|
|
|
2647
2675
|
props.onFocus?.(e);
|
|
2648
2676
|
startAutofillWatch();
|
|
2649
2677
|
scheduleAutofillProbe();
|
|
2650
|
-
|
|
2678
|
+
autofillLater(syncAutofill, 50);
|
|
2651
2679
|
});
|
|
2652
2680
|
input.addEventListener("blur", (e) => {
|
|
2653
2681
|
props.onBlur?.(e);
|
|
2654
2682
|
stopAutofillWatch();
|
|
2655
|
-
|
|
2683
|
+
autofillLater(syncAutofill, 0);
|
|
2656
2684
|
});
|
|
2657
2685
|
input.addEventListener("animationstart", (e) => {
|
|
2658
2686
|
if (e.animationName !== "cms-autofill") return;
|
|
@@ -2660,9 +2688,16 @@ const unitCover = (v, name = 'size') => {
|
|
|
2660
2688
|
startAutofillWatch();
|
|
2661
2689
|
scheduleAutofillProbe();
|
|
2662
2690
|
});
|
|
2663
|
-
|
|
2691
|
+
autofillLater(syncAutofill, 0);
|
|
2664
2692
|
scheduleAutofillProbe();
|
|
2665
2693
|
|
|
2694
|
+
JSswift._registerCleanup(input, () => {
|
|
2695
|
+
autofillDisposed = true;
|
|
2696
|
+
stopAutofillWatch();
|
|
2697
|
+
autofillTimeouts.forEach(clearTimeout);
|
|
2698
|
+
autofillTimeouts.clear();
|
|
2699
|
+
});
|
|
2700
|
+
|
|
2666
2701
|
// model binding
|
|
2667
2702
|
const model = props.model;
|
|
2668
2703
|
if (model) {
|
|
@@ -2676,11 +2711,12 @@ const unitCover = (v, name = 'size') => {
|
|
|
2676
2711
|
lastKnownValue = next;
|
|
2677
2712
|
};
|
|
2678
2713
|
|
|
2679
|
-
|
|
2714
|
+
const onValue = (v) => {
|
|
2680
2715
|
if (syncing) return;
|
|
2681
2716
|
syncing = true;
|
|
2682
2717
|
try { updateFromRod(v); } finally { syncing = false; }
|
|
2683
|
-
}
|
|
2718
|
+
};
|
|
2719
|
+
rod.action(onValue);
|
|
2684
2720
|
|
|
2685
2721
|
// Component setup may run inside a dynamic parent render. This initial
|
|
2686
2722
|
// hydration must not make that parent subscribe to the control model.
|
|
@@ -2697,18 +2733,26 @@ const unitCover = (v, name = 'size') => {
|
|
|
2697
2733
|
input.addEventListener("input", onInput);
|
|
2698
2734
|
input.addEventListener("compositionend", onInput);
|
|
2699
2735
|
|
|
2700
|
-
|
|
2701
|
-
|
|
2702
|
-
|
|
2703
|
-
|
|
2704
|
-
|
|
2705
|
-
|
|
2736
|
+
let disposed = false;
|
|
2737
|
+
const stop = () => {
|
|
2738
|
+
if (disposed) return;
|
|
2739
|
+
disposed = true;
|
|
2740
|
+
input.removeEventListener("input", onInput);
|
|
2741
|
+
input.removeEventListener("compositionend", onInput);
|
|
2742
|
+
const actionIndex = rod._actions?.indexOf(onValue) ?? -1;
|
|
2743
|
+
if (actionIndex >= 0) rod._actions.splice(actionIndex, 1);
|
|
2744
|
+
const hookIndex = rod._disposeHooks?.indexOf(stop) ?? -1;
|
|
2745
|
+
if (hookIndex >= 0 && !rod._disposed) rod._disposeHooks.splice(hookIndex, 1);
|
|
2746
|
+
};
|
|
2747
|
+
rod.onDispose?.(stop);
|
|
2748
|
+
JSswift._registerCleanup(input, stop);
|
|
2706
2749
|
};
|
|
2707
2750
|
if (typeof model === "object" && typeof model._bind === "function") {
|
|
2708
2751
|
bindInputRod(model);
|
|
2709
2752
|
} else if (Array.isArray(model) && typeof model[0] === "function" && typeof model[1] === "function") {
|
|
2710
2753
|
const r = JSswift.reactive.untracked(() => JSswift.rodFromSignal(model[0], model[1]));
|
|
2711
2754
|
bindInputRod(r);
|
|
2755
|
+
JSswift._registerCleanup(input, () => r.dispose());
|
|
2712
2756
|
}
|
|
2713
2757
|
}
|
|
2714
2758
|
|
|
@@ -2792,11 +2836,12 @@ const unitCover = (v, name = 'size') => {
|
|
|
2792
2836
|
textarea.value = next;
|
|
2793
2837
|
};
|
|
2794
2838
|
|
|
2795
|
-
|
|
2839
|
+
const onValue = (v) => {
|
|
2796
2840
|
if (syncing) return;
|
|
2797
2841
|
syncing = true;
|
|
2798
2842
|
try { updateFromRod(v); } finally { syncing = false; }
|
|
2799
|
-
}
|
|
2843
|
+
};
|
|
2844
|
+
rod.action(onValue);
|
|
2800
2845
|
|
|
2801
2846
|
JSswift.reactive.untracked(() => updateFromRod(rod.value));
|
|
2802
2847
|
|
|
@@ -2811,18 +2856,26 @@ const unitCover = (v, name = 'size') => {
|
|
|
2811
2856
|
textarea.addEventListener("input", onInput);
|
|
2812
2857
|
textarea.addEventListener("compositionend", onInput);
|
|
2813
2858
|
|
|
2814
|
-
|
|
2815
|
-
|
|
2816
|
-
|
|
2817
|
-
|
|
2818
|
-
|
|
2819
|
-
|
|
2859
|
+
let disposed = false;
|
|
2860
|
+
const stop = () => {
|
|
2861
|
+
if (disposed) return;
|
|
2862
|
+
disposed = true;
|
|
2863
|
+
textarea.removeEventListener("input", onInput);
|
|
2864
|
+
textarea.removeEventListener("compositionend", onInput);
|
|
2865
|
+
const actionIndex = rod._actions?.indexOf(onValue) ?? -1;
|
|
2866
|
+
if (actionIndex >= 0) rod._actions.splice(actionIndex, 1);
|
|
2867
|
+
const hookIndex = rod._disposeHooks?.indexOf(stop) ?? -1;
|
|
2868
|
+
if (hookIndex >= 0 && !rod._disposed) rod._disposeHooks.splice(hookIndex, 1);
|
|
2869
|
+
};
|
|
2870
|
+
rod.onDispose?.(stop);
|
|
2871
|
+
JSswift._registerCleanup(textarea, stop);
|
|
2820
2872
|
};
|
|
2821
2873
|
if (typeof model === "object" && typeof model._bind === "function") {
|
|
2822
2874
|
bindTextareaRod(model);
|
|
2823
2875
|
} else if (Array.isArray(model) && typeof model[0] === "function" && typeof model[1] === "function") {
|
|
2824
2876
|
const r = JSswift.reactive.untracked(() => JSswift.rodFromSignal(model[0], model[1]));
|
|
2825
2877
|
bindTextareaRod(r);
|
|
2878
|
+
JSswift._registerCleanup(textarea, () => r.dispose());
|
|
2826
2879
|
}
|
|
2827
2880
|
}
|
|
2828
2881
|
|
|
@@ -2858,7 +2911,22 @@ const unitCover = (v, name = 'size') => {
|
|
|
2858
2911
|
};
|
|
2859
2912
|
|
|
2860
2913
|
|
|
2914
|
+
// Component-owned subscriptions; shared models remain caller-owned.
|
|
2915
|
+
function uiSubscriptionScope() {
|
|
2916
|
+
const stops = [];
|
|
2917
|
+
return {
|
|
2918
|
+
effect: (fn, name) => { const stop = JSswift.reactive.effect(fn, name); stops.push(stop); return stop; },
|
|
2919
|
+
action: (rod, fn) => {
|
|
2920
|
+
rod.action(fn);
|
|
2921
|
+
stops.push(() => { const i = rod._actions?.indexOf(fn) ?? -1; if (i >= 0) rod._actions.splice(i, 1); });
|
|
2922
|
+
},
|
|
2923
|
+
dispose: () => stops.splice(0).forEach(stop => stop())
|
|
2924
|
+
};
|
|
2925
|
+
}
|
|
2926
|
+
|
|
2861
2927
|
UI.Search = (props = {}) => {
|
|
2928
|
+
const scope = uiSubscriptionScope();
|
|
2929
|
+
let disposed = false;
|
|
2862
2930
|
const slots = props.slots || {};
|
|
2863
2931
|
const isBoundValue = (v) => !!v && ((typeof v === "object" && typeof v._bind === "function") || uiIsSignal(v));
|
|
2864
2932
|
const valueBinding = isBoundValue(props.model)
|
|
@@ -2971,6 +3039,7 @@ const unitCover = (v, name = 'size') => {
|
|
|
2971
3039
|
setActive(-1);
|
|
2972
3040
|
};
|
|
2973
3041
|
const runSearch = async (query, options = {}) => {
|
|
3042
|
+
if (disposed) return;
|
|
2974
3043
|
const q = query == null ? "" : String(query).trim();
|
|
2975
3044
|
const id = ++requestId;
|
|
2976
3045
|
const endpoint = uiUnwrap(props.endpoint ?? props.url);
|
|
@@ -3025,7 +3094,7 @@ const unitCover = (v, name = 'size') => {
|
|
|
3025
3094
|
} else {
|
|
3026
3095
|
raw = [];
|
|
3027
3096
|
}
|
|
3028
|
-
if (id !== requestId) return;
|
|
3097
|
+
if (disposed || id !== requestId) return;
|
|
3029
3098
|
const mapped = typeof props.mapResponse === "function" ? props.mapResponse(raw, q) : normalizeResults(raw);
|
|
3030
3099
|
const results = normalizeResults(mapped);
|
|
3031
3100
|
if (cacheEnabled) cache.set(cacheKey, results);
|
|
@@ -3034,13 +3103,13 @@ const unitCover = (v, name = 'size') => {
|
|
|
3034
3103
|
props.onResults?.(results, q);
|
|
3035
3104
|
} catch (error) {
|
|
3036
3105
|
if (error?.name === "AbortError") return;
|
|
3037
|
-
if (id !== requestId) return;
|
|
3106
|
+
if (disposed || id !== requestId) return;
|
|
3038
3107
|
setError(error);
|
|
3039
3108
|
setResults([]);
|
|
3040
3109
|
setActive(-1);
|
|
3041
3110
|
props.onError?.(error, q);
|
|
3042
3111
|
} finally {
|
|
3043
|
-
if (id === requestId) {
|
|
3112
|
+
if (!disposed && id === requestId) {
|
|
3044
3113
|
setLoading(false);
|
|
3045
3114
|
activeController = null;
|
|
3046
3115
|
scheduleMenuPosition();
|
|
@@ -3048,6 +3117,7 @@ const unitCover = (v, name = 'size') => {
|
|
|
3048
3117
|
}
|
|
3049
3118
|
};
|
|
3050
3119
|
const scheduleSearch = (query, options = {}) => {
|
|
3120
|
+
if (disposed) return;
|
|
3051
3121
|
if (searchTimer) clearTimeout(searchTimer);
|
|
3052
3122
|
const delay = options.immediate ? 0 : debounceMs();
|
|
3053
3123
|
searchTimer = setTimeout(() => {
|
|
@@ -3208,18 +3278,18 @@ const unitCover = (v, name = 'size') => {
|
|
|
3208
3278
|
|
|
3209
3279
|
if (valueBinding) {
|
|
3210
3280
|
if (typeof valueBinding === "object" && typeof valueBinding._bind === "function") {
|
|
3211
|
-
|
|
3281
|
+
scope.action(valueBinding, (v) => setQuery(v, { fromModel: true }));
|
|
3212
3282
|
modelSet = (v) => {
|
|
3213
3283
|
if (valueBinding.value !== v) valueBinding.value = v;
|
|
3214
3284
|
};
|
|
3215
3285
|
} else if (Array.isArray(valueBinding) && typeof valueBinding[0] === "function" && typeof valueBinding[1] === "function") {
|
|
3216
3286
|
const get = valueBinding[0];
|
|
3217
3287
|
const set = valueBinding[1];
|
|
3218
|
-
|
|
3288
|
+
scope.effect(() => { setQuery(get(), { fromModel: true }); }, "UI.Search:model");
|
|
3219
3289
|
modelSet = (v) => set(v);
|
|
3220
3290
|
}
|
|
3221
3291
|
} else if (uiIsReactive(props.value)) {
|
|
3222
|
-
|
|
3292
|
+
scope.effect(() => { setQuery(uiUnwrap(props.value), { fromModel: true }); }, "UI.Search:value");
|
|
3223
3293
|
}
|
|
3224
3294
|
|
|
3225
3295
|
input.addEventListener("input", (event) => {
|
|
@@ -3293,7 +3363,7 @@ const unitCover = (v, name = 'size') => {
|
|
|
3293
3363
|
};
|
|
3294
3364
|
document.addEventListener("click", onDocClick, true);
|
|
3295
3365
|
|
|
3296
|
-
|
|
3366
|
+
scope.effect(() => {
|
|
3297
3367
|
const openNow = getOpen();
|
|
3298
3368
|
root.classList.toggle("open", openNow);
|
|
3299
3369
|
input.setAttribute("aria-expanded", openNow ? "true" : "false");
|
|
@@ -3301,7 +3371,7 @@ const unitCover = (v, name = 'size') => {
|
|
|
3301
3371
|
else unmountMenuPortal();
|
|
3302
3372
|
}, "UI.Search:open");
|
|
3303
3373
|
|
|
3304
|
-
|
|
3374
|
+
scope.effect(() => {
|
|
3305
3375
|
const d = isDisabled();
|
|
3306
3376
|
const r = isReadonly();
|
|
3307
3377
|
root.classList.toggle("disabled", d);
|
|
@@ -3310,7 +3380,7 @@ const unitCover = (v, name = 'size') => {
|
|
|
3310
3380
|
input.readOnly = r;
|
|
3311
3381
|
}, "UI.Search:disabled");
|
|
3312
3382
|
|
|
3313
|
-
|
|
3383
|
+
scope.effect(() => {
|
|
3314
3384
|
const loading = getLoading();
|
|
3315
3385
|
const error = getError();
|
|
3316
3386
|
const results = getResults();
|
|
@@ -3364,7 +3434,7 @@ const unitCover = (v, name = 'size') => {
|
|
|
3364
3434
|
scheduleMenuPosition();
|
|
3365
3435
|
}, "UI.Search:render");
|
|
3366
3436
|
|
|
3367
|
-
|
|
3437
|
+
scope.effect(() => {
|
|
3368
3438
|
const active = getActive();
|
|
3369
3439
|
if (active === lastActive) return;
|
|
3370
3440
|
if (resultNodes[lastActive]) {
|
|
@@ -3407,10 +3477,12 @@ const unitCover = (v, name = 'size') => {
|
|
|
3407
3477
|
field._searchNow = () => runSearch(getQuery(), { immediate: true, open: true });
|
|
3408
3478
|
field._getResults = getResults;
|
|
3409
3479
|
field._setValue = (value) => setQuery(value, {});
|
|
3410
|
-
let disposed = false;
|
|
3411
3480
|
field._dispose = () => {
|
|
3412
3481
|
if (disposed) return;
|
|
3413
3482
|
disposed = true;
|
|
3483
|
+
scope.dispose();
|
|
3484
|
+
requestId++;
|
|
3485
|
+
cache.clear();
|
|
3414
3486
|
document.removeEventListener("click", onDocClick, true);
|
|
3415
3487
|
if (searchTimer) clearTimeout(searchTimer);
|
|
3416
3488
|
if (activeController) activeController.abort();
|
|
@@ -3431,6 +3503,8 @@ const unitCover = (v, name = 'size') => {
|
|
|
3431
3503
|
|
|
3432
3504
|
|
|
3433
3505
|
UI.Select = (props = {}) => {
|
|
3506
|
+
const scope = uiSubscriptionScope();
|
|
3507
|
+
let disposed = false;
|
|
3434
3508
|
const fieldProps = {
|
|
3435
3509
|
...props,
|
|
3436
3510
|
label: props.label ?? props.placeholder
|
|
@@ -3473,7 +3547,7 @@ const unitCover = (v, name = 'size') => {
|
|
|
3473
3547
|
if (valueBinding) {
|
|
3474
3548
|
if (typeof valueBinding === "object" && typeof valueBinding._bind === "function") {
|
|
3475
3549
|
JSswift.reactive.untracked(() => setValue(normalizeValue(valueBinding.value)));
|
|
3476
|
-
|
|
3550
|
+
scope.action(valueBinding, (v) => setValue(normalizeValue(v)));
|
|
3477
3551
|
modelSet = (v) => {
|
|
3478
3552
|
const next = normalizeValue(v);
|
|
3479
3553
|
if (valueBinding.value !== next) valueBinding.value = next;
|
|
@@ -3482,7 +3556,7 @@ const unitCover = (v, name = 'size') => {
|
|
|
3482
3556
|
const get = valueBinding[0];
|
|
3483
3557
|
const set = valueBinding[1];
|
|
3484
3558
|
JSswift.reactive.untracked(() => {
|
|
3485
|
-
|
|
3559
|
+
scope.effect(() => { setValue(normalizeValue(get())); }, "UI.Select:model");
|
|
3486
3560
|
});
|
|
3487
3561
|
modelSet = (v) => set(normalizeValue(v));
|
|
3488
3562
|
}
|
|
@@ -3549,6 +3623,7 @@ const unitCover = (v, name = 'size') => {
|
|
|
3549
3623
|
|
|
3550
3624
|
// supports async options
|
|
3551
3625
|
async function loadOptions() {
|
|
3626
|
+
if (disposed) return;
|
|
3552
3627
|
const src = props.options;
|
|
3553
3628
|
let raw;
|
|
3554
3629
|
|
|
@@ -3562,12 +3637,13 @@ const unitCover = (v, name = 'size') => {
|
|
|
3562
3637
|
} else {
|
|
3563
3638
|
raw = v;
|
|
3564
3639
|
}
|
|
3565
|
-
buildLists(raw);
|
|
3640
|
+
if (!disposed) buildLists(raw);
|
|
3566
3641
|
} catch (e) {
|
|
3642
|
+
if (disposed) return;
|
|
3567
3643
|
console.error("[UI.Select] options load error:", e);
|
|
3568
3644
|
buildLists([]);
|
|
3569
3645
|
} finally {
|
|
3570
|
-
setLoading(false);
|
|
3646
|
+
if (!disposed) setLoading(false);
|
|
3571
3647
|
}
|
|
3572
3648
|
}
|
|
3573
3649
|
|
|
@@ -3607,11 +3683,12 @@ const unitCover = (v, name = 'size') => {
|
|
|
3607
3683
|
}
|
|
3608
3684
|
|
|
3609
3685
|
async function open() {
|
|
3610
|
-
if (isDisabled()) return;
|
|
3686
|
+
if (disposed || isDisabled()) return;
|
|
3611
3687
|
setOpen(true);
|
|
3612
3688
|
|
|
3613
3689
|
// load options (sync/async) on open
|
|
3614
3690
|
await loadOptions();
|
|
3691
|
+
if (disposed) return;
|
|
3615
3692
|
|
|
3616
3693
|
// set active to current selected, otherwise first enabled
|
|
3617
3694
|
const current = getValue();
|
|
@@ -3649,7 +3726,7 @@ const unitCover = (v, name = 'size') => {
|
|
|
3649
3726
|
const valueNode = _.div({
|
|
3650
3727
|
class: uiClass(["cms-select-value", isMulti ? "cms-select-value-multi" : ""])
|
|
3651
3728
|
});
|
|
3652
|
-
|
|
3729
|
+
scope.effect(() => {
|
|
3653
3730
|
const flat = getFlat();
|
|
3654
3731
|
valueNode.innerHTML = "";
|
|
3655
3732
|
if (isMulti) {
|
|
@@ -3696,7 +3773,7 @@ const unitCover = (v, name = 'size') => {
|
|
|
3696
3773
|
}, "UI.Select:value");
|
|
3697
3774
|
|
|
3698
3775
|
const arrowWrap = _.div({ class: "cms-select-arrow" });
|
|
3699
|
-
|
|
3776
|
+
scope.effect(() => {
|
|
3700
3777
|
const arrowNode = JSswift.ui.renderSlot(slots, "arrow", { open: getOpen() }, UI.Icon("#chevron-down"));
|
|
3701
3778
|
arrowWrap.innerHTML = "";
|
|
3702
3779
|
renderSlotToArray(null, "default", {}, arrowNode).forEach(n => arrowWrap.appendChild(n));
|
|
@@ -3738,7 +3815,7 @@ const unitCover = (v, name = 'size') => {
|
|
|
3738
3815
|
if (filterInput) filterInput.value = getFilter() || "";
|
|
3739
3816
|
};
|
|
3740
3817
|
JSswift.reactive.untracked(renderFilterSlot);
|
|
3741
|
-
|
|
3818
|
+
scope.effect(() => {
|
|
3742
3819
|
const next = getFilter() || "";
|
|
3743
3820
|
if (filterInput && "value" in filterInput && filterInput.value !== next) {
|
|
3744
3821
|
filterInput.value = next;
|
|
@@ -3747,7 +3824,7 @@ const unitCover = (v, name = 'size') => {
|
|
|
3747
3824
|
|
|
3748
3825
|
if (typeof filterable === "boolean") filterable ? filterWrap.classList.remove("cms-d-none") : filterWrap.classList.add("cms-d-none");
|
|
3749
3826
|
else if (filterable?.action) {
|
|
3750
|
-
|
|
3827
|
+
scope.action(filterable, (v) => {
|
|
3751
3828
|
v ? filterWrap.classList.remove("cms-d-none") : filterWrap.classList.add("cms-d-none");
|
|
3752
3829
|
});
|
|
3753
3830
|
filterable.value ? filterWrap.classList.remove("cms-d-none") : filterWrap.classList.add("cms-d-none");;
|
|
@@ -3848,7 +3925,7 @@ const unitCover = (v, name = 'size') => {
|
|
|
3848
3925
|
root.appendChild(menu);
|
|
3849
3926
|
|
|
3850
3927
|
// open class + aria
|
|
3851
|
-
|
|
3928
|
+
scope.effect(() => {
|
|
3852
3929
|
const o = getOpen();
|
|
3853
3930
|
root.classList.toggle("open", o);
|
|
3854
3931
|
root.setAttribute("aria-expanded", o ? "true" : "false");
|
|
@@ -3856,7 +3933,7 @@ const unitCover = (v, name = 'size') => {
|
|
|
3856
3933
|
else unmountMenuPortal();
|
|
3857
3934
|
}, "UI.Select:open");
|
|
3858
3935
|
|
|
3859
|
-
|
|
3936
|
+
scope.effect(() => {
|
|
3860
3937
|
const d = isDisabled();
|
|
3861
3938
|
root.classList.toggle("disabled", d);
|
|
3862
3939
|
root.tabIndex = d ? -1 : 0;
|
|
@@ -3878,7 +3955,7 @@ const unitCover = (v, name = 'size') => {
|
|
|
3878
3955
|
if (active >= 0) optionNodes[active]?.scrollIntoView({ block: "nearest" });
|
|
3879
3956
|
}
|
|
3880
3957
|
|
|
3881
|
-
|
|
3958
|
+
scope.effect(() => {
|
|
3882
3959
|
const display = getList();
|
|
3883
3960
|
const flat = getFlat();
|
|
3884
3961
|
const filter = (getFilter() || "").toLowerCase().trim();
|
|
@@ -3975,7 +4052,7 @@ const unitCover = (v, name = 'size') => {
|
|
|
3975
4052
|
if (getOpen()) scheduleMenuPosition();
|
|
3976
4053
|
}, "UI.Select:render");
|
|
3977
4054
|
|
|
3978
|
-
|
|
4055
|
+
scope.effect(() => {
|
|
3979
4056
|
paintActive();
|
|
3980
4057
|
}, "UI.Select:paintActive");
|
|
3981
4058
|
|
|
@@ -4089,10 +4166,10 @@ const unitCover = (v, name = 'size') => {
|
|
|
4089
4166
|
root.addEventListener("keydown", onKeyDown);
|
|
4090
4167
|
filterWrap.addEventListener("keydown", onKeyDown);
|
|
4091
4168
|
|
|
4092
|
-
let disposed = false;
|
|
4093
4169
|
root._dispose = () => {
|
|
4094
4170
|
if (disposed) return;
|
|
4095
4171
|
disposed = true;
|
|
4172
|
+
scope.dispose();
|
|
4096
4173
|
document.removeEventListener("click", onDocClick, true);
|
|
4097
4174
|
root.removeEventListener("keydown", onKeyDown);
|
|
4098
4175
|
filterWrap.removeEventListener("keydown", onKeyDown);
|
|
@@ -4132,7 +4209,7 @@ const unitCover = (v, name = 'size') => {
|
|
|
4132
4209
|
});
|
|
4133
4210
|
|
|
4134
4211
|
// refresh has-value when value changes
|
|
4135
|
-
|
|
4212
|
+
scope.effect(() => { field._refresh?.(); }, "UI.Select:fieldRefresh");
|
|
4136
4213
|
|
|
4137
4214
|
uiConfigureFieldControl(field, root, props);
|
|
4138
4215
|
field._select = root;
|
|
@@ -4715,7 +4792,10 @@ const unitCover = (v, name = 'size') => {
|
|
|
4715
4792
|
return {
|
|
4716
4793
|
get: () => model.value,
|
|
4717
4794
|
set: (v) => { if (model.value !== v) model.value = v; },
|
|
4718
|
-
watch: (fn) => {
|
|
4795
|
+
watch: (fn) => {
|
|
4796
|
+
model.action?.(fn);
|
|
4797
|
+
return () => { const i = model._actions?.indexOf(fn) ?? -1; if (i >= 0) model._actions.splice(i, 1); };
|
|
4798
|
+
}
|
|
4719
4799
|
};
|
|
4720
4800
|
}
|
|
4721
4801
|
if (Array.isArray(model) && typeof model[0] === "function" && typeof model[1] === "function") {
|
|
@@ -4893,7 +4973,7 @@ const unitCover = (v, name = 'size') => {
|
|
|
4893
4973
|
if (getOpen() !== next) syncOpen(next);
|
|
4894
4974
|
controlledDrawer?.set(next);
|
|
4895
4975
|
};
|
|
4896
|
-
controlledDrawer?.watch?.(() => {
|
|
4976
|
+
const stopDrawerWatch = controlledDrawer?.watch?.(() => {
|
|
4897
4977
|
const next = !!controlledDrawer.get();
|
|
4898
4978
|
if (getOpen() !== next) syncOpen(next);
|
|
4899
4979
|
});
|
|
@@ -4908,7 +4988,7 @@ const unitCover = (v, name = 'size') => {
|
|
|
4908
4988
|
if (getNavOpen() !== next) syncNavOpen(next);
|
|
4909
4989
|
controlledNav?.set(next);
|
|
4910
4990
|
};
|
|
4911
|
-
controlledNav?.watch?.(() => {
|
|
4991
|
+
const stopNavWatch = controlledNav?.watch?.(() => {
|
|
4912
4992
|
const next = !!controlledNav.get();
|
|
4913
4993
|
if (getNavOpen() !== next) syncNavOpen(next);
|
|
4914
4994
|
});
|
|
@@ -4996,6 +5076,8 @@ const unitCover = (v, name = 'size') => {
|
|
|
4996
5076
|
JSswift.uiApplyResponsiveProps(p, props, JSswift.uiResponsiveStyleRules);
|
|
4997
5077
|
|
|
4998
5078
|
const root = _.div(p);
|
|
5079
|
+
if (stopDrawerWatch) JSswift._registerCleanup(root, stopDrawerWatch);
|
|
5080
|
+
if (stopNavWatch) JSswift._registerCleanup(root, stopNavWatch);
|
|
4999
5081
|
const tagPage = uiUnwrap(resolveLayoutProp("tagPage")) === true;
|
|
5000
5082
|
const tags = tagPage
|
|
5001
5083
|
? { header: "header", aside: "aside", nav: "nav", page: "main", footer: "footer" }
|
|
@@ -15898,8 +15980,9 @@ transition: width 200ms ease;
|
|
|
15898
15980
|
};
|
|
15899
15981
|
|
|
15900
15982
|
let searchInput = null;
|
|
15983
|
+
let stopSearchWatch;
|
|
15901
15984
|
if (searchModel) {
|
|
15902
|
-
searchModel.watch((value) => {
|
|
15985
|
+
stopSearchWatch = searchModel.watch((value) => {
|
|
15903
15986
|
const next = String(value ?? "");
|
|
15904
15987
|
setQueryState(next);
|
|
15905
15988
|
if (searchInput && searchInput.value !== next) searchInput.value = next;
|
|
@@ -16109,7 +16192,7 @@ transition: width 200ms ease;
|
|
|
16109
16192
|
);
|
|
16110
16193
|
};
|
|
16111
16194
|
|
|
16112
|
-
app.reactive.effect(() => {
|
|
16195
|
+
const stopRender = app.reactive.effect(() => {
|
|
16113
16196
|
const rows = tableToArray(props.rows);
|
|
16114
16197
|
const query = String(getQuery() || "").trim();
|
|
16115
16198
|
const currentSort = getSort();
|
|
@@ -16283,7 +16366,10 @@ transition: width 200ms ease;
|
|
|
16283
16366
|
}, "UI.Table:render");
|
|
16284
16367
|
|
|
16285
16368
|
wrapProps.body = shell;
|
|
16286
|
-
|
|
16369
|
+
const card = UI.Card(wrapProps);
|
|
16370
|
+
JSswift._registerCleanup(card, stopRender);
|
|
16371
|
+
if (stopSearchWatch) JSswift._registerCleanup(card, stopSearchWatch);
|
|
16372
|
+
return card;
|
|
16287
16373
|
};
|
|
16288
16374
|
UI.Menu = (...args) => {
|
|
16289
16375
|
const { props, children } = JSswift.uiNormalizeArgs(args);
|