jsswift 2.0.1 → 2.1.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/AI.md +5 -0
- package/README.md +2 -0
- package/dist/build/build-configured-runtime.d.mts +29 -0
- package/dist/build/build-configured-runtime.mjs +92 -0
- package/dist/build/runtime-layout.mjs +13 -0
- package/dist/build/select-ui.mjs +167 -0
- package/dist/build/vite-jss.d.mts +2 -0
- package/dist/build/vite-jss.mjs +73 -0
- package/dist/cms.js +3522 -0
- package/dist/documentions/AI.md +5 -0
- package/dist/documentions/README.md +1 -0
- package/dist/documentions/configured-runtime.md +82 -0
- package/dist/documentions/knowledge-map.json +20 -1
- package/dist/ui-src/00-bootstrap.js +330 -0
- package/dist/ui-src/01-foundation-helpers.js +809 -0
- package/dist/ui-src/10-primitives-layout.js +5091 -0
- package/dist/ui-src/20-display-content.js +780 -0
- package/dist/ui-src/21-list-content.js +246 -0
- package/dist/ui-src/22-banner-content.js +376 -0
- package/dist/ui-src/23-tooltip.js +276 -0
- package/dist/ui-src/31-form-advanced.js +2784 -0
- package/dist/ui-src/40-navigation.js +1262 -0
- package/dist/ui-src/41-tab-panel.js +896 -0
- package/dist/ui-src/50-feedback.js +999 -0
- package/dist/ui-src/51-feedback-service.js +166 -0
- package/dist/ui-src/60-shell-app.js +1223 -0
- package/dist/ui-src/61-form-service.js +368 -0
- package/dist/ui-src/70-dialog.js +333 -0
- package/dist/ui-src/71-menu-overlays.js +1217 -0
- package/dist/ui-src/80-data.js +437 -0
- package/dist/ui-src/modules.json +212 -0
- package/dist/ui.js +12565 -0
- package/package.json +26 -2
|
@@ -0,0 +1,1262 @@
|
|
|
1
|
+
UI.Tabs = (...args) => {
|
|
2
|
+
const { props, children } = JSswift.uiNormalizeArgs(args);
|
|
3
|
+
const slots = props.slots || {};
|
|
4
|
+
const hasOwn = (obj, key) => !!obj && Object.prototype.hasOwnProperty.call(obj, key);
|
|
5
|
+
const normalizeOrientation = (value) => String(uiUnwrap(value) || "horizontal").toLowerCase() === "vertical" ? "vertical" : "horizontal";
|
|
6
|
+
const normalizeVariant = (value) => {
|
|
7
|
+
const raw = String(uiUnwrap(value) || "").toLowerCase();
|
|
8
|
+
if (raw === "pills" || raw === "pill") return "pills";
|
|
9
|
+
if (raw === "soft" || raw === "card") return "soft";
|
|
10
|
+
return "line";
|
|
11
|
+
};
|
|
12
|
+
const resolveAccent = (value) => {
|
|
13
|
+
const raw = uiUnwrap(value);
|
|
14
|
+
if (raw == null || raw === "") return null;
|
|
15
|
+
return JSswift.uiColors?.includes(raw) ? `var(--jss-${raw})` : String(raw);
|
|
16
|
+
};
|
|
17
|
+
const isSameValue = (a, b) => Object.is(a, b) || (a != null && b != null && a == b);
|
|
18
|
+
|
|
19
|
+
const model = resolveModel(props.model, "UI.Tabs:model");
|
|
20
|
+
const orientation = normalizeOrientation(props.orientation || props.orient || props.direction);
|
|
21
|
+
const variant = normalizeVariant(props.variant || (props.pills ? "pills" : (props.soft ? "soft" : "line")));
|
|
22
|
+
const fillTabs = !!uiUnwrap(props.fill ?? props.navFill ?? props.stretch);
|
|
23
|
+
const wrapTabs = !!uiUnwrap(props.wrap);
|
|
24
|
+
const disabledAll = !!uiUnwrap(props.disabled);
|
|
25
|
+
const componentId = props.id || (`jss-tabs-` + Math.random().toString(36).slice(2, 9));
|
|
26
|
+
const rawTabs = uiUnwrap(props.tabs ?? props.items) || [];
|
|
27
|
+
const tabs = (Array.isArray(rawTabs) ? rawTabs : [rawTabs])
|
|
28
|
+
.map((tab, index) => {
|
|
29
|
+
if (tab == null) return null;
|
|
30
|
+
if (typeof tab !== "object") {
|
|
31
|
+
return {
|
|
32
|
+
raw: tab,
|
|
33
|
+
index,
|
|
34
|
+
value: tab,
|
|
35
|
+
labelFallback: tab,
|
|
36
|
+
noteFallback: null,
|
|
37
|
+
iconFallback: null,
|
|
38
|
+
badgeFallback: null,
|
|
39
|
+
disabled: false,
|
|
40
|
+
hidden: false
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
const value = tab.value ?? tab.name ?? tab.id ?? tab.key ?? tab.label ?? tab.title ?? `tab-${index}`;
|
|
44
|
+
const labelFallback = tab.label ?? tab.title ?? tab.name ?? tab.value ?? `Tab ${index + 1}`;
|
|
45
|
+
const badgeFallback = hasOwn(tab, "badge")
|
|
46
|
+
? tab.badge
|
|
47
|
+
: (hasOwn(tab, "counter") ? tab.counter : null);
|
|
48
|
+
return {
|
|
49
|
+
...tab,
|
|
50
|
+
raw: tab,
|
|
51
|
+
index,
|
|
52
|
+
value,
|
|
53
|
+
labelFallback,
|
|
54
|
+
noteFallback: tab.note ?? tab.subtitle ?? tab.description ?? null,
|
|
55
|
+
iconFallback: tab.icon ?? null,
|
|
56
|
+
badgeFallback
|
|
57
|
+
};
|
|
58
|
+
})
|
|
59
|
+
.filter(Boolean)
|
|
60
|
+
.filter((tab) => tab.hidden !== true && tab.visible !== false);
|
|
61
|
+
|
|
62
|
+
const firstEnabled = tabs.find((tab) => !tab.disabled) || null;
|
|
63
|
+
const findTabIndex = (value) => tabs.findIndex((tab) => isSameValue(tab.value, value));
|
|
64
|
+
const findEnabledIndex = (startIndex, step) => {
|
|
65
|
+
if (!tabs.length) return -1;
|
|
66
|
+
let index = startIndex;
|
|
67
|
+
for (let i = 0; i < tabs.length; i += 1) {
|
|
68
|
+
index += step;
|
|
69
|
+
if (index < 0) index = tabs.length - 1;
|
|
70
|
+
else if (index >= tabs.length) index = 0;
|
|
71
|
+
if (!tabs[index]?.disabled) return index;
|
|
72
|
+
}
|
|
73
|
+
return -1;
|
|
74
|
+
};
|
|
75
|
+
const coerceValue = (value, fallback = false) => {
|
|
76
|
+
const index = findTabIndex(value);
|
|
77
|
+
if (index >= 0 && !tabs[index]?.disabled) return tabs[index].value;
|
|
78
|
+
return fallback ? (firstEnabled?.value ?? null) : (value ?? null);
|
|
79
|
+
};
|
|
80
|
+
const initialValue = coerceValue(
|
|
81
|
+
model
|
|
82
|
+
? model.get()
|
|
83
|
+
: (hasOwn(props, "value") ? uiUnwrap(props.value) : (props.defaultValue ?? props.default)),
|
|
84
|
+
true
|
|
85
|
+
);
|
|
86
|
+
const [getValue, setValue] = JSswift.reactive.signal(initialValue);
|
|
87
|
+
|
|
88
|
+
const cls = uiClass([
|
|
89
|
+
"jss-clear-set",
|
|
90
|
+
"jss-tabs",
|
|
91
|
+
"jss-singularity",
|
|
92
|
+
orientation,
|
|
93
|
+
`variant-${variant}`,
|
|
94
|
+
uiWhen(props.dense, "dense"),
|
|
95
|
+
uiWhen(fillTabs, "fill"),
|
|
96
|
+
uiWhen(wrapTabs, "wrap"),
|
|
97
|
+
uiWhen(disabledAll, "disabled"),
|
|
98
|
+
props.class
|
|
99
|
+
]);
|
|
100
|
+
const wrapProps = JSswift.omit(props, [
|
|
101
|
+
"tabs", "items", "value", "defaultValue", "default", "model",
|
|
102
|
+
"orientation", "orient", "direction",
|
|
103
|
+
"variant", "pills", "soft",
|
|
104
|
+
"fill", "navFill", "stretch",
|
|
105
|
+
"wrap", "dense", "disabled",
|
|
106
|
+
"slots", "empty",
|
|
107
|
+
"gap", "tabGap",
|
|
108
|
+
"align", "justify",
|
|
109
|
+
"color", "state",
|
|
110
|
+
"navClass", "navStyle",
|
|
111
|
+
"barClass", "barStyle",
|
|
112
|
+
"tabClass", "tabStyle",
|
|
113
|
+
"itemClass", "itemStyle",
|
|
114
|
+
"class", "style"
|
|
115
|
+
]);
|
|
116
|
+
wrapProps.class = cls;
|
|
117
|
+
wrapProps.style = {
|
|
118
|
+
...(props.style || {})
|
|
119
|
+
};
|
|
120
|
+
if (props.gap != null) wrapProps.style["--jss-tabs-gap"] = toCssSize(props.gap);
|
|
121
|
+
if (props.tabGap != null) wrapProps.style["--jss-tabs-tab-gap"] = toCssSize(props.tabGap);
|
|
122
|
+
const accentColor = resolveAccent(props.color ?? props.state);
|
|
123
|
+
if (accentColor) wrapProps.style["--jss-tabs-accent"] = accentColor;
|
|
124
|
+
const wrap = _.div(wrapProps);
|
|
125
|
+
|
|
126
|
+
const bar = _.div({
|
|
127
|
+
class: uiClass(["jss-tabs-bar", props.barClass]),
|
|
128
|
+
style: {
|
|
129
|
+
alignItems: props.align || (orientation === "vertical" ? "stretch" : "center"),
|
|
130
|
+
justifyContent: props.justify || "flex-start",
|
|
131
|
+
...(props.barStyle || {})
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
const nav = _.div({
|
|
135
|
+
class: uiClass(["jss-tabs-nav", props.navClass]),
|
|
136
|
+
style: props.navStyle || null,
|
|
137
|
+
role: "tablist",
|
|
138
|
+
"aria-orientation": orientation
|
|
139
|
+
});
|
|
140
|
+
const tabButtons = [];
|
|
141
|
+
|
|
142
|
+
const getActiveIndex = () => findTabIndex(getValue());
|
|
143
|
+
const getActiveTab = () => {
|
|
144
|
+
const index = getActiveIndex();
|
|
145
|
+
return index >= 0 ? tabs[index] : null;
|
|
146
|
+
};
|
|
147
|
+
const createRootCtx = () => ({
|
|
148
|
+
tabs,
|
|
149
|
+
value: getValue(),
|
|
150
|
+
active: getValue(),
|
|
151
|
+
activeIndex: getActiveIndex(),
|
|
152
|
+
activeTab: getActiveTab(),
|
|
153
|
+
orientation,
|
|
154
|
+
variant,
|
|
155
|
+
select: (value, event) => selectByValue(value, { event, emit: true }),
|
|
156
|
+
next: () => goNext(),
|
|
157
|
+
prev: () => goPrev()
|
|
158
|
+
});
|
|
159
|
+
const createCtx = (tab, index, active) => ({
|
|
160
|
+
...createRootCtx(),
|
|
161
|
+
tab,
|
|
162
|
+
index,
|
|
163
|
+
active,
|
|
164
|
+
disabled: !!tab.disabled,
|
|
165
|
+
select: (event) => selectByIndex(index, { event, emit: true })
|
|
166
|
+
});
|
|
167
|
+
const focusIndex = (index) => {
|
|
168
|
+
const btn = tabButtons[index]?.btn;
|
|
169
|
+
if (!btn || typeof btn.focus !== "function") return;
|
|
170
|
+
requestAnimationFrame(() => btn.focus());
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
const updateButtons = (nextValue) => {
|
|
174
|
+
const nextIndex = findTabIndex(nextValue);
|
|
175
|
+
wrap.dataset.value = nextValue == null ? "" : String(nextValue);
|
|
176
|
+
wrap.dataset.activeIndex = nextIndex >= 0 ? String(nextIndex) : "";
|
|
177
|
+
tabButtons.forEach(({ btn, item, tab, index }) => {
|
|
178
|
+
const active = index === nextIndex;
|
|
179
|
+
item.classList.toggle("active", active);
|
|
180
|
+
item.classList.toggle("disabled", !!(tab.disabled || disabledAll));
|
|
181
|
+
btn.classList.toggle("active", active);
|
|
182
|
+
btn.setAttribute("aria-selected", active ? "true" : "false");
|
|
183
|
+
btn.setAttribute("tabindex", active ? "0" : "-1");
|
|
184
|
+
});
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
const syncValue = (nextValue) => {
|
|
188
|
+
setValue(nextValue);
|
|
189
|
+
updateButtons(nextValue);
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
const selectByValue = (nextValue, options = {}) => {
|
|
193
|
+
if (disabledAll) return null;
|
|
194
|
+
const index = findTabIndex(nextValue);
|
|
195
|
+
if (index < 0) return null;
|
|
196
|
+
return selectByIndex(index, options);
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
const selectByIndex = (index, options = {}) => {
|
|
200
|
+
const tab = tabs[index];
|
|
201
|
+
if (!tab || tab.disabled || disabledAll) return null;
|
|
202
|
+
const nextValue = tab.value;
|
|
203
|
+
const prevValue = getValue();
|
|
204
|
+
const changed = !isSameValue(prevValue, nextValue);
|
|
205
|
+
|
|
206
|
+
syncValue(nextValue);
|
|
207
|
+
if (model && !isSameValue(model.get(), nextValue)) {
|
|
208
|
+
model.set(nextValue);
|
|
209
|
+
}
|
|
210
|
+
if (options.focus) focusIndex(index);
|
|
211
|
+
if (options.event) {
|
|
212
|
+
tab.onClick?.(nextValue, options.event, tab, index);
|
|
213
|
+
}
|
|
214
|
+
if (changed && options.emit !== false) {
|
|
215
|
+
props.onChange?.(nextValue, tab, options.event || null, index);
|
|
216
|
+
}
|
|
217
|
+
return tab;
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
const goNext = () => {
|
|
221
|
+
const nextIndex = findEnabledIndex(getActiveIndex() < 0 ? -1 : getActiveIndex(), 1);
|
|
222
|
+
if (nextIndex >= 0) selectByIndex(nextIndex, { emit: true, focus: true });
|
|
223
|
+
};
|
|
224
|
+
const goPrev = () => {
|
|
225
|
+
const start = getActiveIndex() < 0 ? 0 : getActiveIndex();
|
|
226
|
+
const prevIndex = findEnabledIndex(start, -1);
|
|
227
|
+
if (prevIndex >= 0) selectByIndex(prevIndex, { emit: true, focus: true });
|
|
228
|
+
};
|
|
229
|
+
|
|
230
|
+
const makeTabNode = (tab, index) => {
|
|
231
|
+
const isActive = index === getActiveIndex();
|
|
232
|
+
const ctx = createCtx(tab, index, isActive);
|
|
233
|
+
const tabId = `${componentId}-tab-${index}`;
|
|
234
|
+
const iconFallback = tab.iconFallback != null
|
|
235
|
+
? (typeof tab.iconFallback === "string"
|
|
236
|
+
? UI.Icon({ name: tab.iconFallback, size: tab.iconSize ?? tab.size ?? props.size ?? null })
|
|
237
|
+
: tab.iconFallback)
|
|
238
|
+
: null;
|
|
239
|
+
const iconNodes = renderSlotToArray(slots, "icon", ctx, iconFallback);
|
|
240
|
+
const labelNodes = renderSlotToArray(null, "default", {}, JSswift.ui.renderSlot(slots, "label", ctx, tab.labelFallback));
|
|
241
|
+
const noteNodes = renderSlotToArray(slots, "note", ctx, tab.noteFallback);
|
|
242
|
+
const badgeNodes = renderSlotToArray(slots, "badge", ctx, tab.badgeFallback);
|
|
243
|
+
const fallbackContent = _.span({ class: "jss-tabs-tab-inner" },
|
|
244
|
+
iconNodes.length ? _.span({ class: "jss-tabs-tab-icon" }, ...iconNodes) : null,
|
|
245
|
+
_.span({ class: "jss-tabs-tab-copy" },
|
|
246
|
+
_.span({ class: "jss-tabs-tab-label" }, ...(labelNodes.length ? labelNodes : [""])),
|
|
247
|
+
noteNodes.length ? _.span({ class: "jss-tabs-tab-note" }, ...noteNodes) : null
|
|
248
|
+
),
|
|
249
|
+
badgeNodes.length ? _.span({ class: "jss-tabs-tab-badge" }, ...badgeNodes) : null
|
|
250
|
+
);
|
|
251
|
+
const contentNodes = renderSlotToArray(slots, "tab", {
|
|
252
|
+
...ctx,
|
|
253
|
+
tabId,
|
|
254
|
+
label: labelNodes,
|
|
255
|
+
icon: iconNodes,
|
|
256
|
+
note: noteNodes,
|
|
257
|
+
badge: badgeNodes
|
|
258
|
+
}, fallbackContent);
|
|
259
|
+
const onKeydown = (e) => {
|
|
260
|
+
if (tab.disabled || disabledAll) return;
|
|
261
|
+
const key = e.key;
|
|
262
|
+
if (key === "Enter" || key === " ") {
|
|
263
|
+
e.preventDefault();
|
|
264
|
+
selectByIndex(index, { event: e, emit: true });
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
if (key === "Home") {
|
|
268
|
+
e.preventDefault();
|
|
269
|
+
const firstIndex = tabs.findIndex((item) => !item.disabled);
|
|
270
|
+
if (firstIndex >= 0) selectByIndex(firstIndex, { event: e, emit: true, focus: true });
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
if (key === "End") {
|
|
274
|
+
e.preventDefault();
|
|
275
|
+
let lastIndex = -1;
|
|
276
|
+
for (let i = tabs.length - 1; i >= 0; i -= 1) {
|
|
277
|
+
if (!tabs[i]?.disabled) {
|
|
278
|
+
lastIndex = i;
|
|
279
|
+
break;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
if (lastIndex >= 0) selectByIndex(lastIndex, { event: e, emit: true, focus: true });
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
const prevKeys = orientation === "vertical" ? ["ArrowUp"] : ["ArrowLeft"];
|
|
286
|
+
const nextKeys = orientation === "vertical" ? ["ArrowDown"] : ["ArrowRight"];
|
|
287
|
+
if (prevKeys.includes(key)) {
|
|
288
|
+
e.preventDefault();
|
|
289
|
+
const prevIndex = findEnabledIndex(index, -1);
|
|
290
|
+
if (prevIndex >= 0) selectByIndex(prevIndex, { event: e, emit: true, focus: true });
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
293
|
+
if (nextKeys.includes(key)) {
|
|
294
|
+
e.preventDefault();
|
|
295
|
+
const nextIndex = findEnabledIndex(index, 1);
|
|
296
|
+
if (nextIndex >= 0) selectByIndex(nextIndex, { event: e, emit: true, focus: true });
|
|
297
|
+
}
|
|
298
|
+
};
|
|
299
|
+
const itemAccent = resolveAccent(tab.color ?? tab.state);
|
|
300
|
+
const item = _.div({
|
|
301
|
+
class: uiClass([
|
|
302
|
+
"jss-tabs-item",
|
|
303
|
+
uiWhen(isActive, "active"),
|
|
304
|
+
uiWhen(tab.disabled || disabledAll, "disabled"),
|
|
305
|
+
props.itemClass,
|
|
306
|
+
tab.itemClass
|
|
307
|
+
]),
|
|
308
|
+
style: {
|
|
309
|
+
...(itemAccent ? { "--jss-tabs-item-accent": itemAccent } : {}),
|
|
310
|
+
...(props.itemStyle || {}),
|
|
311
|
+
...(tab.itemStyle || {})
|
|
312
|
+
},
|
|
313
|
+
"data-name": String(tab.value)
|
|
314
|
+
});
|
|
315
|
+
const btn = UI.Btn({
|
|
316
|
+
class: uiClass([
|
|
317
|
+
"jss-tabs-tab",
|
|
318
|
+
uiWhen(isActive, "active"),
|
|
319
|
+
uiWhen(tab.disabled || disabledAll, "disabled"),
|
|
320
|
+
props.tabClass,
|
|
321
|
+
tab.tabClass || tab.class
|
|
322
|
+
]),
|
|
323
|
+
type: "button",
|
|
324
|
+
id: tabId,
|
|
325
|
+
role: "tab",
|
|
326
|
+
disabled: !!(tab.disabled || disabledAll),
|
|
327
|
+
"aria-selected": isActive ? "true" : "false",
|
|
328
|
+
tabindex: isActive ? "0" : "-1",
|
|
329
|
+
size: tab.size ?? props.size ?? null,
|
|
330
|
+
style: {
|
|
331
|
+
...(props.tabStyle || {}),
|
|
332
|
+
...(tab.tabStyle || {})
|
|
333
|
+
},
|
|
334
|
+
onClick: (e) => {
|
|
335
|
+
selectByIndex(index, { event: e, emit: true });
|
|
336
|
+
},
|
|
337
|
+
onKeydown
|
|
338
|
+
}, ...contentNodes);
|
|
339
|
+
item.appendChild(btn);
|
|
340
|
+
item.appendChild(_.span({ class: "tab-indicator" }));
|
|
341
|
+
tabButtons[index] = { tab, index, item, btn };
|
|
342
|
+
return item;
|
|
343
|
+
};
|
|
344
|
+
|
|
345
|
+
if (tabs.length) {
|
|
346
|
+
const defaultNavNodes = tabs.map((tab, index) => makeTabNode(tab, index));
|
|
347
|
+
const navContent = JSswift.ui.renderSlot(slots, "nav", {
|
|
348
|
+
...createRootCtx(),
|
|
349
|
+
nodes: defaultNavNodes
|
|
350
|
+
}, defaultNavNodes);
|
|
351
|
+
renderSlotToArray(null, "default", {}, navContent).forEach((node) => nav.appendChild(node));
|
|
352
|
+
} else {
|
|
353
|
+
renderSlotToArray(slots, "empty", createRootCtx(), props.empty ?? _.div({ class: "jss-tabs-empty" }, "Nessuna tab disponibile."))
|
|
354
|
+
.forEach((node) => nav.appendChild(node));
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
const extraCtx = createRootCtx();
|
|
358
|
+
const extraNodes = [
|
|
359
|
+
...renderSlotToArray(null, "default", {}, JSswift.ui.renderSlot(slots, "extra", extraCtx, null)),
|
|
360
|
+
...renderSlotToArray(slots, "default", extraCtx, children)
|
|
361
|
+
];
|
|
362
|
+
bar.appendChild(nav);
|
|
363
|
+
if (extraNodes.length) {
|
|
364
|
+
bar.appendChild(_.div({ class: "jss-tabs-extra" }, ...extraNodes));
|
|
365
|
+
}
|
|
366
|
+
wrap.appendChild(bar);
|
|
367
|
+
|
|
368
|
+
syncValue(initialValue);
|
|
369
|
+
if (model) {
|
|
370
|
+
model.watch((value) => {
|
|
371
|
+
syncValue(coerceValue(value, true));
|
|
372
|
+
}, "UI.Tabs:watch");
|
|
373
|
+
} else if (uiIsReactive(props.value)) {
|
|
374
|
+
app.reactive.effect(() => {
|
|
375
|
+
syncValue(coerceValue(uiUnwrap(props.value), true));
|
|
376
|
+
}, "UI.Tabs:value");
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
wrap._getValue = () => getValue();
|
|
380
|
+
wrap._setValue = (value) => selectByValue(value, { emit: true });
|
|
381
|
+
wrap._select = (value) => selectByValue(value, { emit: true });
|
|
382
|
+
wrap._next = () => goNext();
|
|
383
|
+
wrap._prev = () => goPrev();
|
|
384
|
+
wrap._getTabs = () => tabs.slice();
|
|
385
|
+
setPropertyProps(wrap, props);
|
|
386
|
+
return wrap;
|
|
387
|
+
};
|
|
388
|
+
|
|
389
|
+
// Esempio: JSswift.ui.Tabs({ tabs: [{ label: "Overview", value: "overview", icon: "dashboard" }], model: [get,set] })
|
|
390
|
+
|
|
391
|
+
UI.RouteTab = (...args) => {
|
|
392
|
+
const { props, children } = JSswift.uiNormalizeArgs(args);
|
|
393
|
+
const slots = props.slots || {};
|
|
394
|
+
const router = JSswift.router || app?.router || null;
|
|
395
|
+
const hasOwn = (key) => Object.prototype.hasOwnProperty.call(props, key);
|
|
396
|
+
const hasExplicitActive = hasOwn("active") || hasOwn("selected");
|
|
397
|
+
const normalizeVariant = (value) => {
|
|
398
|
+
const raw = String(uiUnwrap(value) || "").toLowerCase();
|
|
399
|
+
if (raw === "pill" || raw === "pills") return "pills";
|
|
400
|
+
if (raw === "soft" || raw === "card") return "soft";
|
|
401
|
+
return "line";
|
|
402
|
+
};
|
|
403
|
+
const resolveAccent = (value) => {
|
|
404
|
+
const raw = uiUnwrap(value);
|
|
405
|
+
if (raw == null || raw === "") return null;
|
|
406
|
+
return JSswift.uiColors?.includes(raw) ? `var(--jss-${raw})` : String(raw);
|
|
407
|
+
};
|
|
408
|
+
const resolvePath = (value) => {
|
|
409
|
+
if (value == null || value === "") return "";
|
|
410
|
+
try {
|
|
411
|
+
const url = new URL(String(value), window.location.origin);
|
|
412
|
+
return url.pathname || "";
|
|
413
|
+
} catch (_) {
|
|
414
|
+
return String(value).split(/[?#]/)[0] || "";
|
|
415
|
+
}
|
|
416
|
+
};
|
|
417
|
+
const isExternalLink = (value) => {
|
|
418
|
+
if (uiUnwrap(props.external) === true) return true;
|
|
419
|
+
if (!value) return false;
|
|
420
|
+
return /^(mailto:|tel:|https?:\/\/|\/\/)/i.test(String(value));
|
|
421
|
+
};
|
|
422
|
+
const getHref = () => uiUnwrap(props.href ?? props.to) || "#";
|
|
423
|
+
const getTo = () => uiUnwrap(props.to ?? props.href) || "";
|
|
424
|
+
const getCurrentPath = () => {
|
|
425
|
+
try {
|
|
426
|
+
if (router?.current) return router.current().pathname || window.location.pathname || "";
|
|
427
|
+
} catch (_) { }
|
|
428
|
+
return window.location.pathname || "";
|
|
429
|
+
};
|
|
430
|
+
const matchCurrentRoute = () => {
|
|
431
|
+
const to = getTo();
|
|
432
|
+
if (!to) return false;
|
|
433
|
+
const matchValue = uiUnwrap(props.match ?? props.activeMatch ?? props.routeMatch);
|
|
434
|
+
const matchMode = String(uiUnwrap(props.matchMode ?? (props.startsWith ? "startsWith" : (props.exact ? "exact" : ""))) || "").toLowerCase();
|
|
435
|
+
const currentPath = getCurrentPath();
|
|
436
|
+
if (typeof matchValue === "function") {
|
|
437
|
+
return !!matchValue({ currentPath, to, href: getHref(), router, props });
|
|
438
|
+
}
|
|
439
|
+
if (matchValue instanceof RegExp) {
|
|
440
|
+
return matchValue.test(currentPath);
|
|
441
|
+
}
|
|
442
|
+
const targetPath = resolvePath(typeof matchValue === "string" && !["exact", "startsWith", "prefix", "section"].includes(matchValue.toLowerCase())
|
|
443
|
+
? matchValue
|
|
444
|
+
: to);
|
|
445
|
+
if (!targetPath) return false;
|
|
446
|
+
const mode = matchMode || (typeof matchValue === "string" && ["startsWith", "prefix", "section"].includes(matchValue.toLowerCase()) ? "startsWith" : "exact");
|
|
447
|
+
if (mode === "startswith" || mode === "prefix" || mode === "section") {
|
|
448
|
+
return currentPath === targetPath || currentPath.startsWith(`${targetPath}/`);
|
|
449
|
+
}
|
|
450
|
+
if (router?.isActive) return !!router.isActive(to);
|
|
451
|
+
return currentPath === targetPath;
|
|
452
|
+
};
|
|
453
|
+
const getDisabled = () => !!uiUnwrap(props.disabled);
|
|
454
|
+
let manualActive = null;
|
|
455
|
+
const getActive = () => {
|
|
456
|
+
if (manualActive != null) return !!manualActive;
|
|
457
|
+
if (hasExplicitActive) return !!uiUnwrap(props.active ?? props.selected);
|
|
458
|
+
return matchCurrentRoute();
|
|
459
|
+
};
|
|
460
|
+
|
|
461
|
+
const variant = normalizeVariant(props.variant || (props.pills ? "pills" : (props.soft ? "soft" : "line")));
|
|
462
|
+
const normalizedState = !uiUnwrap(props.color) && uiUnwrap(props.state)
|
|
463
|
+
? normalizeState(uiUnwrap(props.state))
|
|
464
|
+
: "";
|
|
465
|
+
const stateClass = normalizedState ? `jss-state-${normalizedState}` : "";
|
|
466
|
+
const p = JSswift.omit(props, [
|
|
467
|
+
"active", "selected", "label", "title", "text", "to", "icon", "iconRight", "iconSize",
|
|
468
|
+
"note", "subtitle", "caption", "description", "badge", "counter", "count", "aside", "trailing",
|
|
469
|
+
"slots", "state", "color", "dense", "outline", "flat", "glossy", "glow", "glass", "gradient",
|
|
470
|
+
"textGradient", "lightShadow", "shadow", "rounded", "radius", "textColor", "variant", "pills",
|
|
471
|
+
"soft", "block", "fill", "match", "matchMode", "activeMatch", "routeMatch", "exact",
|
|
472
|
+
"startsWith", "external", "onNavigate"
|
|
473
|
+
]);
|
|
474
|
+
p.href = getHref();
|
|
475
|
+
p.class = uiClass([
|
|
476
|
+
"jss-route-tab",
|
|
477
|
+
"jss-clear-set",
|
|
478
|
+
"jss-singularity",
|
|
479
|
+
`variant-${variant}`,
|
|
480
|
+
uiWhen(props.block ?? props.fill, "block"),
|
|
481
|
+
uiWhen(props.dense, "dense"),
|
|
482
|
+
stateClass,
|
|
483
|
+
props.class
|
|
484
|
+
]);
|
|
485
|
+
p.style = {
|
|
486
|
+
...(props.style || {})
|
|
487
|
+
};
|
|
488
|
+
|
|
489
|
+
const accent = resolveAccent(props.color ?? props.state);
|
|
490
|
+
if (accent) p.style["--jss-route-tab-accent"] = accent;
|
|
491
|
+
|
|
492
|
+
const iconSize = props.iconSize ?? props.size ?? null;
|
|
493
|
+
const resolveIconNode = (value, as) => {
|
|
494
|
+
const raw = uiUnwrap(value);
|
|
495
|
+
if (raw == null || raw === false || raw === "") return null;
|
|
496
|
+
if (typeof raw === "string") return UI.Icon({ name: raw, size: iconSize });
|
|
497
|
+
return JSswift.ui.slot(raw, { as });
|
|
498
|
+
};
|
|
499
|
+
const ctx = () => ({
|
|
500
|
+
active: getActive(),
|
|
501
|
+
disabled: getDisabled(),
|
|
502
|
+
href: getHref(),
|
|
503
|
+
to: getTo(),
|
|
504
|
+
external: isExternalLink(getHref()),
|
|
505
|
+
navigate: (event) => {
|
|
506
|
+
if (getDisabled()) return false;
|
|
507
|
+
const nextTo = getTo();
|
|
508
|
+
const external = isExternalLink(getHref());
|
|
509
|
+
if (!external && nextTo && router?.navigate) {
|
|
510
|
+
if (event?.preventDefault) event.preventDefault();
|
|
511
|
+
router.navigate(nextTo);
|
|
512
|
+
props.onNavigate?.(nextTo, event || null);
|
|
513
|
+
return true;
|
|
514
|
+
}
|
|
515
|
+
props.onNavigate?.(nextTo || getHref(), event || null);
|
|
516
|
+
return false;
|
|
517
|
+
}
|
|
518
|
+
});
|
|
519
|
+
|
|
520
|
+
const iconNodes = renderSlotToArray(slots, "icon", ctx(), resolveIconNode(props.icon, "icon"));
|
|
521
|
+
const labelNodes = renderSlotToArray(
|
|
522
|
+
slots,
|
|
523
|
+
"label",
|
|
524
|
+
ctx(),
|
|
525
|
+
props.label ?? props.title ?? props.text ?? children
|
|
526
|
+
);
|
|
527
|
+
const noteNodes = renderSlotToArray(
|
|
528
|
+
slots,
|
|
529
|
+
"note",
|
|
530
|
+
ctx(),
|
|
531
|
+
props.note ?? props.subtitle ?? props.caption ?? props.description
|
|
532
|
+
);
|
|
533
|
+
const badgeNodes = renderSlotToArray(
|
|
534
|
+
slots,
|
|
535
|
+
"badge",
|
|
536
|
+
ctx(),
|
|
537
|
+
props.badge ?? props.counter ?? props.count
|
|
538
|
+
);
|
|
539
|
+
const asideNodes = renderSlotToArray(
|
|
540
|
+
slots,
|
|
541
|
+
"aside",
|
|
542
|
+
ctx(),
|
|
543
|
+
props.aside ?? props.trailing ?? resolveIconNode(props.iconRight, "iconRight")
|
|
544
|
+
);
|
|
545
|
+
const defaultNodes = renderSlotToArray(slots, "default", ctx(), props.content ?? props.body);
|
|
546
|
+
|
|
547
|
+
const content = _.span({ class: "jss-route-tab-inner" },
|
|
548
|
+
iconNodes.length ? _.span({ class: "jss-route-tab-icon" }, ...iconNodes) : null,
|
|
549
|
+
_.span({ class: "jss-route-tab-copy" },
|
|
550
|
+
_.span({ class: "jss-route-tab-label" }, ...(labelNodes.length ? labelNodes : [getTo() || getHref() || ""])),
|
|
551
|
+
noteNodes.length ? _.span({ class: "jss-route-tab-note" }, ...noteNodes) : null,
|
|
552
|
+
defaultNodes.length ? _.span({ class: "jss-route-tab-extra" }, ...defaultNodes) : null
|
|
553
|
+
),
|
|
554
|
+
badgeNodes.length ? _.span({ class: "jss-route-tab-badge" }, ...badgeNodes) : null,
|
|
555
|
+
asideNodes.length ? _.span({ class: "jss-route-tab-aside" }, ...asideNodes) : null
|
|
556
|
+
);
|
|
557
|
+
|
|
558
|
+
const wrap = _.a(p, content);
|
|
559
|
+
setPropertyProps(wrap, props);
|
|
560
|
+
|
|
561
|
+
const syncState = () => {
|
|
562
|
+
const active = getActive();
|
|
563
|
+
const disabled = getDisabled();
|
|
564
|
+
const href = getHref();
|
|
565
|
+
const external = isExternalLink(href);
|
|
566
|
+
wrap.classList.toggle("active", active);
|
|
567
|
+
wrap.classList.toggle("is-disabled", disabled);
|
|
568
|
+
wrap.classList.toggle("is-external", external);
|
|
569
|
+
if (active) wrap.setAttribute("aria-current", String(uiUnwrap(props.ariaCurrent) || "page"));
|
|
570
|
+
else wrap.removeAttribute("aria-current");
|
|
571
|
+
wrap.setAttribute("href", href || "#");
|
|
572
|
+
if (disabled) {
|
|
573
|
+
wrap.setAttribute("aria-disabled", "true");
|
|
574
|
+
wrap.tabIndex = -1;
|
|
575
|
+
} else {
|
|
576
|
+
wrap.removeAttribute("aria-disabled");
|
|
577
|
+
if (!hasOwn("tabIndex") && !hasOwn("tabindex")) wrap.removeAttribute("tabindex");
|
|
578
|
+
}
|
|
579
|
+
if (getTo()) wrap.dataset.to = String(getTo());
|
|
580
|
+
else delete wrap.dataset.to;
|
|
581
|
+
if (external && wrap.target === "_blank" && !wrap.rel) wrap.rel = "noopener noreferrer";
|
|
582
|
+
};
|
|
583
|
+
|
|
584
|
+
const userOnClick = props.onClick;
|
|
585
|
+
wrap.onclick = (e) => {
|
|
586
|
+
userOnClick?.(e);
|
|
587
|
+
if (e.defaultPrevented) return;
|
|
588
|
+
if (getDisabled()) {
|
|
589
|
+
e.preventDefault();
|
|
590
|
+
return;
|
|
591
|
+
}
|
|
592
|
+
ctx().navigate(e);
|
|
593
|
+
};
|
|
594
|
+
|
|
595
|
+
if (!hasExplicitActive && getTo() && router?.subscribe) {
|
|
596
|
+
router.subscribe(() => syncState());
|
|
597
|
+
}
|
|
598
|
+
JSswift.reactive.effect(() => {
|
|
599
|
+
syncState();
|
|
600
|
+
}, "UI.RouteTab:render");
|
|
601
|
+
|
|
602
|
+
wrap._isActive = () => getActive();
|
|
603
|
+
wrap._setActive = (value) => {
|
|
604
|
+
manualActive = value == null ? null : !!value;
|
|
605
|
+
syncState();
|
|
606
|
+
return wrap;
|
|
607
|
+
};
|
|
608
|
+
wrap._navigate = (event) => ctx().navigate(event);
|
|
609
|
+
return wrap;
|
|
610
|
+
};
|
|
611
|
+
|
|
612
|
+
// Esempio: JSswift.ui.RouteTab({ label: "Home", to: "/" })
|
|
613
|
+
|
|
614
|
+
UI.Breadcrumbs = (...args) => {
|
|
615
|
+
const { props, children } = JSswift.uiNormalizeArgs(args);
|
|
616
|
+
const slots = props.slots || {};
|
|
617
|
+
const router = JSswift.router || app?.router || null;
|
|
618
|
+
const model = resolveModel(props.model, "UI.Breadcrumbs:model");
|
|
619
|
+
const sizeClass = uiComputed(props.size, () => {
|
|
620
|
+
const v = uiUnwrap(props.size);
|
|
621
|
+
return (typeof v === "string" && JSswift.uiSizes?.includes(v)) ? `jss-size-${v}` : "";
|
|
622
|
+
});
|
|
623
|
+
const variantClass = uiComputed(props.variant, () => {
|
|
624
|
+
const raw = String(uiUnwrap(props.variant || (props.pills ? "pills" : (props.soft ? "soft" : "line"))) || "").toLowerCase();
|
|
625
|
+
if (raw === "pill" || raw === "pills") return "variant-pills";
|
|
626
|
+
if (raw === "soft" || raw === "card") return "variant-soft";
|
|
627
|
+
return "variant-line";
|
|
628
|
+
});
|
|
629
|
+
const nowrapClass = uiComputed([props.wrap, props.nowrap], () => {
|
|
630
|
+
return (uiUnwrap(props.wrap) === false || !!uiUnwrap(props.nowrap)) ? "no-wrap" : "";
|
|
631
|
+
});
|
|
632
|
+
const wrap = _.nav({
|
|
633
|
+
class: uiClass([
|
|
634
|
+
"jss-breadcrumbs",
|
|
635
|
+
"jss-clear-set",
|
|
636
|
+
sizeClass,
|
|
637
|
+
variantClass,
|
|
638
|
+
nowrapClass,
|
|
639
|
+
uiWhen(props.dense, "dense"),
|
|
640
|
+
props.class
|
|
641
|
+
]),
|
|
642
|
+
style: { ...(props.style || {}) },
|
|
643
|
+
role: "navigation",
|
|
644
|
+
"aria-label": props.ariaLabel || "Breadcrumb"
|
|
645
|
+
});
|
|
646
|
+
const beforeEl = _.div({ class: "jss-breadcrumbs-before" });
|
|
647
|
+
const listEl = _.ol({ class: "jss-breadcrumbs-list" });
|
|
648
|
+
const afterEl = _.div({ class: "jss-breadcrumbs-after" });
|
|
649
|
+
wrap.appendChild(beforeEl);
|
|
650
|
+
wrap.appendChild(listEl);
|
|
651
|
+
wrap.appendChild(afterEl);
|
|
652
|
+
|
|
653
|
+
const resolveAccent = (value) => {
|
|
654
|
+
const raw = uiUnwrap(value);
|
|
655
|
+
if (raw == null || raw === "") return null;
|
|
656
|
+
return JSswift.uiColors?.includes(raw) ? `var(--jss-${raw})` : String(raw);
|
|
657
|
+
};
|
|
658
|
+
const isExternalLink = (entry) => {
|
|
659
|
+
if (!entry) return false;
|
|
660
|
+
if (uiUnwrap(entry.external) === true) return true;
|
|
661
|
+
const href = uiUnwrap(entry.href ?? entry.to);
|
|
662
|
+
if (!href) return false;
|
|
663
|
+
return /^(mailto:|tel:|https?:\/\/|\/\/)/i.test(String(href));
|
|
664
|
+
};
|
|
665
|
+
const cloneIfNode = (value) => {
|
|
666
|
+
if (value?.nodeType && value.cloneNode) return value.cloneNode(true);
|
|
667
|
+
return value;
|
|
668
|
+
};
|
|
669
|
+
const renderInto = (host, nodes) => {
|
|
670
|
+
host.innerHTML = "";
|
|
671
|
+
nodes.forEach((node) => host.appendChild(node));
|
|
672
|
+
};
|
|
673
|
+
const renderNamedSlot = (slotBag, name, ctx, fallback, alias = null) => {
|
|
674
|
+
const primary = JSswift.ui.getSlot(slotBag, name) != null
|
|
675
|
+
? renderSlotToArray(slotBag, name, ctx, fallback)
|
|
676
|
+
: [];
|
|
677
|
+
if (primary.length) return primary;
|
|
678
|
+
if (alias && JSswift.ui.getSlot(slotBag, alias) != null) {
|
|
679
|
+
return renderSlotToArray(slotBag, alias, ctx, fallback);
|
|
680
|
+
}
|
|
681
|
+
return renderSlotToArray(null, "default", ctx, fallback);
|
|
682
|
+
};
|
|
683
|
+
const resolveIconNode = (value, size, as) => {
|
|
684
|
+
const raw = uiUnwrap(value);
|
|
685
|
+
if (raw == null || raw === false || raw === "") return null;
|
|
686
|
+
if (typeof raw === "string") return UI.Icon({ name: raw, size });
|
|
687
|
+
return JSswift.ui.slot(raw, { as });
|
|
688
|
+
};
|
|
689
|
+
const normalizeItems = () => {
|
|
690
|
+
const source = model ? model.get() : uiUnwrap(props.items ?? props.value ?? props.breadcrumbs);
|
|
691
|
+
const list = [];
|
|
692
|
+
const pushItem = (entry) => {
|
|
693
|
+
if (entry == null || entry === false) return;
|
|
694
|
+
if (Array.isArray(entry)) {
|
|
695
|
+
entry.forEach(pushItem);
|
|
696
|
+
return;
|
|
697
|
+
}
|
|
698
|
+
if (typeof entry === "string" || typeof entry === "number") {
|
|
699
|
+
list.push({ label: String(entry) });
|
|
700
|
+
return;
|
|
701
|
+
}
|
|
702
|
+
if (entry?.nodeType || typeof entry === "function") {
|
|
703
|
+
list.push({ label: entry });
|
|
704
|
+
return;
|
|
705
|
+
}
|
|
706
|
+
list.push(entry);
|
|
707
|
+
};
|
|
708
|
+
|
|
709
|
+
const home = uiUnwrap(props.home);
|
|
710
|
+
if (home) {
|
|
711
|
+
pushItem(home === true ? { label: "Home", to: "/", icon: "home" } : home);
|
|
712
|
+
}
|
|
713
|
+
pushItem(source);
|
|
714
|
+
if (!list.length && children?.length) {
|
|
715
|
+
children.forEach((child) => pushItem(child));
|
|
716
|
+
}
|
|
717
|
+
return list.filter((item) => !uiUnwrap(item?.hidden));
|
|
718
|
+
};
|
|
719
|
+
const getCurrentIndex = (items) => {
|
|
720
|
+
const explicitIndex = items.findIndex((item) => !!uiUnwrap(item?.current ?? item?.active ?? item?.selected));
|
|
721
|
+
if (explicitIndex > -1) return explicitIndex;
|
|
722
|
+
return uiUnwrap(props.autoCurrent) === false ? -1 : (items.length ? items.length - 1 : -1);
|
|
723
|
+
};
|
|
724
|
+
const collapseItems = (items) => {
|
|
725
|
+
const maxRaw = Number(uiUnwrap(props.max ?? props.maxItems ?? props.collapseAfter ?? props.collapse));
|
|
726
|
+
const max = Number.isFinite(maxRaw) ? Math.max(0, Math.trunc(maxRaw)) : 0;
|
|
727
|
+
if (!max || max < 3 || items.length <= max) {
|
|
728
|
+
return items.map((item, index) => ({ type: "item", item, index, key: `item-${index}` }));
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
let leading = Number(uiUnwrap(props.leadingCount ?? props.keepStart ?? 1));
|
|
732
|
+
let trailing = Number(uiUnwrap(props.trailingCount ?? props.keepEnd ?? Math.max(1, max - 2)));
|
|
733
|
+
leading = Number.isFinite(leading) ? Math.max(1, Math.trunc(leading)) : 1;
|
|
734
|
+
trailing = Number.isFinite(trailing) ? Math.max(1, Math.trunc(trailing)) : Math.max(1, max - 2);
|
|
735
|
+
if (leading + trailing + 1 > max) {
|
|
736
|
+
trailing = Math.max(1, max - leading - 1);
|
|
737
|
+
if (leading + trailing + 1 > max) leading = Math.max(1, max - trailing - 1);
|
|
738
|
+
}
|
|
739
|
+
if (leading + trailing >= items.length) {
|
|
740
|
+
return items.map((item, index) => ({ type: "item", item, index, key: `item-${index}` }));
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
const entries = [];
|
|
744
|
+
for (let i = 0; i < leading; i++) {
|
|
745
|
+
entries.push({ type: "item", item: items[i], index: i, key: `item-${i}` });
|
|
746
|
+
}
|
|
747
|
+
const hiddenItems = items.slice(leading, items.length - trailing);
|
|
748
|
+
entries.push({
|
|
749
|
+
type: "collapsed",
|
|
750
|
+
key: `collapsed-${leading}-${items.length - trailing}`,
|
|
751
|
+
items: hiddenItems,
|
|
752
|
+
from: leading,
|
|
753
|
+
to: items.length - trailing - 1
|
|
754
|
+
});
|
|
755
|
+
for (let i = items.length - trailing; i < items.length; i++) {
|
|
756
|
+
entries.push({ type: "item", item: items[i], index: i, key: `item-${i}` });
|
|
757
|
+
}
|
|
758
|
+
return entries;
|
|
759
|
+
};
|
|
760
|
+
const navigateItem = (item, event = null) => {
|
|
761
|
+
if (!item) return false;
|
|
762
|
+
const href = uiUnwrap(item.href ?? item.to) || "";
|
|
763
|
+
const to = uiUnwrap(item.to) || "";
|
|
764
|
+
if (!isExternalLink(item) && to && router?.navigate) {
|
|
765
|
+
if (event?.preventDefault) event.preventDefault();
|
|
766
|
+
router.navigate(to);
|
|
767
|
+
item.onNavigate?.(to, event || null);
|
|
768
|
+
props.onNavigate?.(to, item, event || null);
|
|
769
|
+
return true;
|
|
770
|
+
}
|
|
771
|
+
item.onNavigate?.(to || href, event || null);
|
|
772
|
+
props.onNavigate?.(to || href, item, event || null);
|
|
773
|
+
return false;
|
|
774
|
+
};
|
|
775
|
+
const createSeparator = (entry, index, total) => {
|
|
776
|
+
const prev = entry;
|
|
777
|
+
const next = total[index + 1] || null;
|
|
778
|
+
const ctx = { index, entry, previous: prev, next };
|
|
779
|
+
const fallback = cloneIfNode(uiUnwrap((entry?.type === "item" ? entry.item?.separator : null) ?? props.separator ?? "/"));
|
|
780
|
+
const nodes = renderNamedSlot(slots, "separator", ctx, fallback);
|
|
781
|
+
return _.li({ class: "jss-breadcrumbs-separator-item", "aria-hidden": "true" },
|
|
782
|
+
_.span({ class: "jss-breadcrumbs-separator" }, ...nodes)
|
|
783
|
+
);
|
|
784
|
+
};
|
|
785
|
+
const createCollapsedEntry = (entry, items, currentIndex) => {
|
|
786
|
+
const ctx = {
|
|
787
|
+
type: "collapsed",
|
|
788
|
+
collapsed: true,
|
|
789
|
+
items,
|
|
790
|
+
hiddenItems: entry.items,
|
|
791
|
+
currentIndex,
|
|
792
|
+
from: entry.from,
|
|
793
|
+
to: entry.to
|
|
794
|
+
};
|
|
795
|
+
const fallback = _.span({ class: "jss-breadcrumb jss-breadcrumb-collapsed" },
|
|
796
|
+
_.span({ class: "jss-breadcrumb-inner" },
|
|
797
|
+
_.span({ class: "jss-breadcrumb-copy" },
|
|
798
|
+
_.span({ class: "jss-breadcrumb-label" }, String(uiUnwrap(props.collapsedLabel) || "…"))
|
|
799
|
+
)
|
|
800
|
+
)
|
|
801
|
+
);
|
|
802
|
+
const nodes = renderNamedSlot(slots, "collapsed", ctx, fallback, "item");
|
|
803
|
+
return _.li({ class: "jss-breadcrumbs-entry is-collapsed" }, ...nodes);
|
|
804
|
+
};
|
|
805
|
+
const createItemEntry = (entry, items, currentIndex, visibleIndex) => {
|
|
806
|
+
const item = entry.item || {};
|
|
807
|
+
const slotBag = item.slots ? { ...slots, ...item.slots } : slots;
|
|
808
|
+
const href = uiUnwrap(item.href ?? item.to) || "";
|
|
809
|
+
const disabled = !!uiUnwrap(item.disabled);
|
|
810
|
+
const current = entry.index === currentIndex;
|
|
811
|
+
const iconSize = item.iconSize ?? props.itemSize ?? props.size ?? null;
|
|
812
|
+
const accent = resolveAccent(item.color ?? item.state ?? props.color ?? props.state);
|
|
813
|
+
const state = !uiUnwrap(item.color) && uiUnwrap(item.state)
|
|
814
|
+
? normalizeState(uiUnwrap(item.state))
|
|
815
|
+
: "";
|
|
816
|
+
const labelFallback = item.label ?? item.title ?? item.text ?? item.to ?? item.href ?? "";
|
|
817
|
+
const noteFallback = item.note ?? item.subtitle ?? item.caption ?? item.description;
|
|
818
|
+
const badgeFallback = item.badge ?? item.counter ?? item.count;
|
|
819
|
+
const asideFallback = item.aside ?? item.trailing ?? resolveIconNode(item.iconRight, iconSize, "iconRight");
|
|
820
|
+
const defaultFallback = item.content ?? item.body;
|
|
821
|
+
const ctx = {
|
|
822
|
+
item,
|
|
823
|
+
index: entry.index,
|
|
824
|
+
visibleIndex,
|
|
825
|
+
total: items.length,
|
|
826
|
+
current,
|
|
827
|
+
active: current,
|
|
828
|
+
disabled,
|
|
829
|
+
href,
|
|
830
|
+
to: uiUnwrap(item.to) || "",
|
|
831
|
+
external: isExternalLink(item),
|
|
832
|
+
isFirst: entry.index === 0,
|
|
833
|
+
isLast: entry.index === items.length - 1,
|
|
834
|
+
navigate: (event) => navigateItem(item, event)
|
|
835
|
+
};
|
|
836
|
+
|
|
837
|
+
const iconNodes = renderNamedSlot(slotBag, "icon", ctx, resolveIconNode(item.icon, iconSize, "icon"));
|
|
838
|
+
const labelNodes = renderNamedSlot(slotBag, "label", ctx, labelFallback);
|
|
839
|
+
const noteNodes = renderNamedSlot(slotBag, "note", ctx, noteFallback);
|
|
840
|
+
const badgeNodes = renderNamedSlot(slotBag, "badge", ctx, badgeFallback);
|
|
841
|
+
const asideNodes = renderNamedSlot(slotBag, "aside", ctx, asideFallback);
|
|
842
|
+
const defaultNodes = renderNamedSlot(slotBag, "default", ctx, defaultFallback);
|
|
843
|
+
|
|
844
|
+
const content = _.span({ class: "jss-breadcrumb-inner" },
|
|
845
|
+
iconNodes.length ? _.span({ class: "jss-breadcrumb-icon" }, ...iconNodes) : null,
|
|
846
|
+
_.span({ class: "jss-breadcrumb-copy" },
|
|
847
|
+
_.span({ class: "jss-breadcrumb-label" }, ...(labelNodes.length ? labelNodes : [href || ""])),
|
|
848
|
+
noteNodes.length ? _.span({ class: "jss-breadcrumb-note" }, ...noteNodes) : null,
|
|
849
|
+
defaultNodes.length ? _.span({ class: "jss-breadcrumb-extra" }, ...defaultNodes) : null
|
|
850
|
+
),
|
|
851
|
+
badgeNodes.length ? _.span({ class: "jss-breadcrumb-badge" }, ...badgeNodes) : null,
|
|
852
|
+
asideNodes.length ? _.span({ class: "jss-breadcrumb-aside" }, ...asideNodes) : null
|
|
853
|
+
);
|
|
854
|
+
|
|
855
|
+
const interactive = !disabled && (!current || !!uiUnwrap(item.linkCurrent ?? props.linkCurrent));
|
|
856
|
+
const sharedProps = {
|
|
857
|
+
class: uiClass([
|
|
858
|
+
"jss-breadcrumb",
|
|
859
|
+
"jss-clear-set",
|
|
860
|
+
"jss-singularity",
|
|
861
|
+
uiWhen(current, "is-current"),
|
|
862
|
+
uiWhen(current, "active"),
|
|
863
|
+
uiWhen(disabled, "is-disabled"),
|
|
864
|
+
state ? `jss-state-${state}` : "",
|
|
865
|
+
item.class
|
|
866
|
+
]),
|
|
867
|
+
style: {
|
|
868
|
+
...(item.style || {})
|
|
869
|
+
},
|
|
870
|
+
"aria-current": current ? String(uiUnwrap(item.ariaCurrent ?? props.ariaCurrent) || "page") : null
|
|
871
|
+
};
|
|
872
|
+
if (accent) sharedProps.style["--jss-breadcrumb-accent"] = accent;
|
|
873
|
+
|
|
874
|
+
let node = null;
|
|
875
|
+
if (interactive && href) {
|
|
876
|
+
node = _.a({
|
|
877
|
+
...sharedProps,
|
|
878
|
+
href,
|
|
879
|
+
target: item.target,
|
|
880
|
+
rel: item.rel,
|
|
881
|
+
onClick: (event) => {
|
|
882
|
+
item.onClick?.(event, ctx);
|
|
883
|
+
props.onItemClick?.(item, ctx, event);
|
|
884
|
+
if (event.defaultPrevented) return;
|
|
885
|
+
ctx.navigate(event);
|
|
886
|
+
}
|
|
887
|
+
}, content);
|
|
888
|
+
if (ctx.external && node.target === "_blank" && !node.rel) node.rel = "noopener noreferrer";
|
|
889
|
+
} else if (interactive && typeof item.onClick === "function") {
|
|
890
|
+
node = _.button({
|
|
891
|
+
...sharedProps,
|
|
892
|
+
type: item.type || "button",
|
|
893
|
+
onClick: (event) => {
|
|
894
|
+
item.onClick?.(event, ctx);
|
|
895
|
+
props.onItemClick?.(item, ctx, event);
|
|
896
|
+
}
|
|
897
|
+
}, content);
|
|
898
|
+
} else {
|
|
899
|
+
node = _.span({
|
|
900
|
+
...sharedProps,
|
|
901
|
+
"aria-disabled": disabled ? "true" : null
|
|
902
|
+
}, content);
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
setPropertyProps(node, {
|
|
906
|
+
size: item.size ?? props.itemSize ?? props.size,
|
|
907
|
+
radius: item.radius ?? props.radius,
|
|
908
|
+
gradient: item.gradient ?? props.gradient
|
|
909
|
+
});
|
|
910
|
+
|
|
911
|
+
const fallback = node;
|
|
912
|
+
const rendered = renderNamedSlot(slotBag, "item", ctx, fallback);
|
|
913
|
+
return _.li({
|
|
914
|
+
class: uiClass([
|
|
915
|
+
"jss-breadcrumbs-entry",
|
|
916
|
+
uiWhen(current, "is-current"),
|
|
917
|
+
uiWhen(disabled, "is-disabled")
|
|
918
|
+
])
|
|
919
|
+
}, ...rendered);
|
|
920
|
+
};
|
|
921
|
+
const render = () => {
|
|
922
|
+
const items = normalizeItems();
|
|
923
|
+
const currentIndex = getCurrentIndex(items);
|
|
924
|
+
const visibleItems = collapseItems(items);
|
|
925
|
+
const accent = resolveAccent(props.color ?? props.state);
|
|
926
|
+
wrap.classList.toggle("is-empty", items.length === 0);
|
|
927
|
+
if (accent) wrap.style.setProperty("--jss-breadcrumb-accent", accent);
|
|
928
|
+
else wrap.style.removeProperty("--jss-breadcrumb-accent");
|
|
929
|
+
|
|
930
|
+
const rootCtx = {
|
|
931
|
+
items,
|
|
932
|
+
visibleItems,
|
|
933
|
+
currentIndex,
|
|
934
|
+
count: items.length,
|
|
935
|
+
collapsed: visibleItems.some((entry) => entry.type === "collapsed")
|
|
936
|
+
};
|
|
937
|
+
renderInto(beforeEl, renderNamedSlot(slots, "before", rootCtx, props.before));
|
|
938
|
+
renderInto(afterEl, renderNamedSlot(slots, "after", rootCtx, props.after));
|
|
939
|
+
|
|
940
|
+
listEl.innerHTML = "";
|
|
941
|
+
if (!items.length) {
|
|
942
|
+
const emptyNodes = renderNamedSlot(slots, "empty", rootCtx, props.empty);
|
|
943
|
+
if (emptyNodes.length) listEl.appendChild(_.li({ class: "jss-breadcrumbs-entry is-empty" }, ...emptyNodes));
|
|
944
|
+
return;
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
visibleItems.forEach((entry, visibleIndex) => {
|
|
948
|
+
if (visibleIndex > 0) listEl.appendChild(createSeparator(visibleItems[visibleIndex - 1], visibleIndex - 1, visibleItems));
|
|
949
|
+
listEl.appendChild(
|
|
950
|
+
entry.type === "collapsed"
|
|
951
|
+
? createCollapsedEntry(entry, items, currentIndex)
|
|
952
|
+
: createItemEntry(entry, items, currentIndex, visibleIndex)
|
|
953
|
+
);
|
|
954
|
+
});
|
|
955
|
+
};
|
|
956
|
+
|
|
957
|
+
app.reactive.effect(() => {
|
|
958
|
+
render();
|
|
959
|
+
}, "UI.Breadcrumbs:render");
|
|
960
|
+
|
|
961
|
+
wrap.getItems = () => normalizeItems();
|
|
962
|
+
wrap.getVisibleItems = () => collapseItems(normalizeItems());
|
|
963
|
+
wrap.refresh = () => {
|
|
964
|
+
render();
|
|
965
|
+
return wrap;
|
|
966
|
+
};
|
|
967
|
+
wrap.setItems = (next) => {
|
|
968
|
+
if (model) model.set(next);
|
|
969
|
+
else props.items = next;
|
|
970
|
+
render();
|
|
971
|
+
return wrap;
|
|
972
|
+
};
|
|
973
|
+
setPropertyProps(wrap, props);
|
|
974
|
+
return wrap;
|
|
975
|
+
};
|
|
976
|
+
|
|
977
|
+
// Esempio: JSswift.ui.Breadcrumbs({ items: [{ label: "Home", to: "/" }, { label: "Pagina" }] })
|
|
978
|
+
|
|
979
|
+
UI.Pagination = (...args) => {
|
|
980
|
+
const { props } = JSswift.uiNormalizeArgs(args);
|
|
981
|
+
const slots = props.slots || {};
|
|
982
|
+
const model = resolveModel(props.model, "UI.Pagination:model");
|
|
983
|
+
const sizeClass = uiComputed(props.size, () => {
|
|
984
|
+
const v = uiUnwrap(props.size);
|
|
985
|
+
return (typeof v === "string" && sizeMap[v]) ? `jss-size-${v}` : "";
|
|
986
|
+
});
|
|
987
|
+
const wrap = _.nav({
|
|
988
|
+
class: uiClass([
|
|
989
|
+
"jss-pagination",
|
|
990
|
+
"jss-clear-set",
|
|
991
|
+
sizeClass,
|
|
992
|
+
uiWhen(props.dense, "dense"),
|
|
993
|
+
uiWhen(props.simple, "is-simple"),
|
|
994
|
+
uiWhen(props.disabled, "is-disabled"),
|
|
995
|
+
props.class
|
|
996
|
+
]),
|
|
997
|
+
style: { ...(props.style || {}) },
|
|
998
|
+
role: "navigation",
|
|
999
|
+
"aria-label": props.ariaLabel || "Pagination"
|
|
1000
|
+
});
|
|
1001
|
+
const controls = _.div({ class: "jss-pagination-controls" });
|
|
1002
|
+
const startGroup = _.div({ class: "jss-pagination-start" });
|
|
1003
|
+
const pagesGroup = _.div({ class: "jss-pagination-pages" });
|
|
1004
|
+
const endGroup = _.div({ class: "jss-pagination-end" });
|
|
1005
|
+
const summary = _.div({ class: "jss-pagination-summary" });
|
|
1006
|
+
|
|
1007
|
+
controls.appendChild(startGroup);
|
|
1008
|
+
controls.appendChild(pagesGroup);
|
|
1009
|
+
controls.appendChild(endGroup);
|
|
1010
|
+
wrap.appendChild(controls);
|
|
1011
|
+
wrap.appendChild(summary);
|
|
1012
|
+
|
|
1013
|
+
let currentPage = 1;
|
|
1014
|
+
let currentPages = 1;
|
|
1015
|
+
|
|
1016
|
+
const toInt = (value, fallback = 0) => {
|
|
1017
|
+
const raw = uiUnwrap(value);
|
|
1018
|
+
if (raw == null || raw === "") return fallback;
|
|
1019
|
+
const n = Number(raw);
|
|
1020
|
+
if (!Number.isFinite(n)) return fallback;
|
|
1021
|
+
return Math.trunc(n);
|
|
1022
|
+
};
|
|
1023
|
+
const clampPage = (page, pages = getPages()) => {
|
|
1024
|
+
const raw = Number(page);
|
|
1025
|
+
const next = Number.isFinite(raw) ? Math.trunc(raw) : 1;
|
|
1026
|
+
return Math.min(Math.max(next || 1, 1), Math.max(1, pages || 1));
|
|
1027
|
+
};
|
|
1028
|
+
const getPages = () => {
|
|
1029
|
+
const explicit = toInt(props.max ?? props.pages ?? props.pageCount ?? props.totalPages, 0);
|
|
1030
|
+
if (explicit > 0) return explicit;
|
|
1031
|
+
const total = toInt(props.total ?? props.totalItems ?? props.count, 0);
|
|
1032
|
+
const pageSize = toInt(props.pageSize ?? props.perPage ?? props.limit, 0);
|
|
1033
|
+
if (total >= 0 && pageSize > 0) return Math.max(1, Math.ceil(total / pageSize));
|
|
1034
|
+
return 1;
|
|
1035
|
+
};
|
|
1036
|
+
const getRequestedPage = () => {
|
|
1037
|
+
if (model) return toInt(model.get(), 1);
|
|
1038
|
+
return toInt(props.page ?? props.value ?? props.current ?? props.currentPage, 1);
|
|
1039
|
+
};
|
|
1040
|
+
const getSiblingCount = () => Math.max(0, toInt(props.siblings ?? props.siblingCount ?? props.window, 1));
|
|
1041
|
+
const getBoundaryCount = () => Math.max(0, toInt(props.boundaryCount ?? props.boundaries ?? props.edges, 1));
|
|
1042
|
+
const isDisabled = () => !!uiUnwrap(props.disabled);
|
|
1043
|
+
const showSummary = () => uiUnwrap(props.showSummary ?? props.showLabel) !== false;
|
|
1044
|
+
const showNumbers = () => uiUnwrap(props.showPages) !== false && !uiUnwrap(props.simple);
|
|
1045
|
+
const showPrev = () => uiUnwrap(props.showPrev) !== false;
|
|
1046
|
+
const showNext = () => uiUnwrap(props.showNext) !== false;
|
|
1047
|
+
const showEdges = () => !!uiUnwrap(props.showEdges);
|
|
1048
|
+
const showFirst = () => {
|
|
1049
|
+
const value = uiUnwrap(props.showFirst);
|
|
1050
|
+
return value == null ? showEdges() : !!value;
|
|
1051
|
+
};
|
|
1052
|
+
const showLast = () => {
|
|
1053
|
+
const value = uiUnwrap(props.showLast);
|
|
1054
|
+
return value == null ? showEdges() : !!value;
|
|
1055
|
+
};
|
|
1056
|
+
const hideOnSinglePage = () => !!uiUnwrap(props.hideOnSinglePage);
|
|
1057
|
+
const getTotal = () => {
|
|
1058
|
+
const total = uiUnwrap(props.total ?? props.totalItems ?? props.count);
|
|
1059
|
+
if (total == null || total === "") return null;
|
|
1060
|
+
const n = Number(total);
|
|
1061
|
+
return Number.isFinite(n) ? Math.max(0, Math.trunc(n)) : null;
|
|
1062
|
+
};
|
|
1063
|
+
const getPageSize = () => {
|
|
1064
|
+
const pageSize = uiUnwrap(props.pageSize ?? props.perPage ?? props.limit);
|
|
1065
|
+
if (pageSize == null || pageSize === "") return null;
|
|
1066
|
+
const n = Number(pageSize);
|
|
1067
|
+
return Number.isFinite(n) && n > 0 ? Math.trunc(n) : null;
|
|
1068
|
+
};
|
|
1069
|
+
const getCtx = (page = currentPage, pages = currentPages) => {
|
|
1070
|
+
const total = getTotal();
|
|
1071
|
+
const pageSize = getPageSize();
|
|
1072
|
+
const from = total != null && pageSize ? (total === 0 ? 0 : ((page - 1) * pageSize) + 1) : null;
|
|
1073
|
+
const to = total != null && pageSize ? Math.min(total, page * pageSize) : null;
|
|
1074
|
+
return {
|
|
1075
|
+
page,
|
|
1076
|
+
value: page,
|
|
1077
|
+
current: page,
|
|
1078
|
+
currentPage: page,
|
|
1079
|
+
pages,
|
|
1080
|
+
max: pages,
|
|
1081
|
+
total,
|
|
1082
|
+
pageSize,
|
|
1083
|
+
from,
|
|
1084
|
+
to,
|
|
1085
|
+
disabled: isDisabled(),
|
|
1086
|
+
isFirst: page <= 1,
|
|
1087
|
+
isLast: page >= pages,
|
|
1088
|
+
canPrev: page > 1,
|
|
1089
|
+
canNext: page < pages,
|
|
1090
|
+
setPage: (nextPage, event) => commitPage(nextPage, event),
|
|
1091
|
+
prev: (event) => commitPage(page - 1, event),
|
|
1092
|
+
next: (event) => commitPage(page + 1, event),
|
|
1093
|
+
first: (event) => commitPage(1, event),
|
|
1094
|
+
last: (event) => commitPage(pages, event)
|
|
1095
|
+
};
|
|
1096
|
+
};
|
|
1097
|
+
const buildItems = (page, pages) => {
|
|
1098
|
+
if (pages <= 0) return [];
|
|
1099
|
+
const items = [];
|
|
1100
|
+
const included = new Set();
|
|
1101
|
+
const boundary = getBoundaryCount();
|
|
1102
|
+
const siblings = getSiblingCount();
|
|
1103
|
+
|
|
1104
|
+
for (let i = 1; i <= Math.min(boundary, pages); i++) included.add(i);
|
|
1105
|
+
for (let i = Math.max(1, page - siblings); i <= Math.min(pages, page + siblings); i++) included.add(i);
|
|
1106
|
+
for (let i = Math.max(1, pages - boundary + 1); i <= pages; i++) included.add(i);
|
|
1107
|
+
|
|
1108
|
+
let last = 0;
|
|
1109
|
+
for (let i = 1; i <= pages; i++) {
|
|
1110
|
+
if (!included.has(i)) continue;
|
|
1111
|
+
if (last && i - last > 1) items.push({ type: "ellipsis", key: `ellipsis-${last}-${i}` });
|
|
1112
|
+
items.push({ type: "page", page: i, key: `page-${i}` });
|
|
1113
|
+
last = i;
|
|
1114
|
+
}
|
|
1115
|
+
if (!items.length) items.push({ type: "page", page: 1, key: "page-1" });
|
|
1116
|
+
return items;
|
|
1117
|
+
};
|
|
1118
|
+
const renderNamedSlot = (name, fallback, ctx, alias = null) => {
|
|
1119
|
+
const primary = JSswift.ui.getSlot(slots, name) != null
|
|
1120
|
+
? renderSlotToArray(slots, name, ctx, fallback)
|
|
1121
|
+
: [];
|
|
1122
|
+
if (primary.length) return primary;
|
|
1123
|
+
if (alias && JSswift.ui.getSlot(slots, alias) != null) {
|
|
1124
|
+
return renderSlotToArray(slots, alias, ctx, fallback);
|
|
1125
|
+
}
|
|
1126
|
+
return renderSlotToArray(null, "default", ctx, fallback);
|
|
1127
|
+
};
|
|
1128
|
+
const renderInto = (host, nodes) => {
|
|
1129
|
+
host.innerHTML = "";
|
|
1130
|
+
nodes.forEach((node) => host.appendChild(node));
|
|
1131
|
+
};
|
|
1132
|
+
const createButton = (name, fallback, onClick, options = {}) => {
|
|
1133
|
+
const ctx = { ...getCtx(), kind: name, active: !!options.active };
|
|
1134
|
+
return UI.Btn({
|
|
1135
|
+
class: uiClass([
|
|
1136
|
+
"jss-pagination-btn",
|
|
1137
|
+
`jss-pagination-${name}`,
|
|
1138
|
+
uiWhen(options.active, "active"),
|
|
1139
|
+
options.class
|
|
1140
|
+
]),
|
|
1141
|
+
size: props.itemSize || props.size || (props.dense ? "sm" : "md"),
|
|
1142
|
+
color: options.active ? (props.color || props.state || "primary") : (options.color || null),
|
|
1143
|
+
outline: options.active ? false : (options.outline ?? true),
|
|
1144
|
+
disabled: isDisabled() || !!options.disabled,
|
|
1145
|
+
"aria-current": options.active ? "page" : null,
|
|
1146
|
+
onClick: (event) => {
|
|
1147
|
+
if (isDisabled() || options.disabled) return;
|
|
1148
|
+
onClick?.(event);
|
|
1149
|
+
}
|
|
1150
|
+
}, ...renderNamedSlot(name, fallback, ctx, name === "summary" ? "label" : (name === "page" ? "item" : null)));
|
|
1151
|
+
};
|
|
1152
|
+
const render = (page, pages) => {
|
|
1153
|
+
currentPage = clampPage(page, pages);
|
|
1154
|
+
currentPages = Math.max(1, pages || 1);
|
|
1155
|
+
const ctx = getCtx(currentPage, currentPages);
|
|
1156
|
+
|
|
1157
|
+
wrap.style.display = hideOnSinglePage() && currentPages <= 1 ? "none" : "";
|
|
1158
|
+
summary.style.display = showSummary() ? "" : "none";
|
|
1159
|
+
pagesGroup.style.display = showNumbers() ? "" : "none";
|
|
1160
|
+
|
|
1161
|
+
startGroup.innerHTML = "";
|
|
1162
|
+
pagesGroup.innerHTML = "";
|
|
1163
|
+
endGroup.innerHTML = "";
|
|
1164
|
+
|
|
1165
|
+
const startNodes = renderNamedSlot("start", props.start, ctx);
|
|
1166
|
+
if (startNodes.length) {
|
|
1167
|
+
startGroup.appendChild(_.div({ class: "jss-pagination-extra jss-pagination-extra-start" }, ...startNodes));
|
|
1168
|
+
}
|
|
1169
|
+
|
|
1170
|
+
if (showFirst()) {
|
|
1171
|
+
startGroup.appendChild(createButton("first", "«", (event) => commitPage(1, event), {
|
|
1172
|
+
disabled: currentPage <= 1
|
|
1173
|
+
}));
|
|
1174
|
+
}
|
|
1175
|
+
if (showPrev()) {
|
|
1176
|
+
startGroup.appendChild(createButton("prev", "Prev", (event) => commitPage(currentPage - 1, event), {
|
|
1177
|
+
disabled: currentPage <= 1
|
|
1178
|
+
}));
|
|
1179
|
+
}
|
|
1180
|
+
|
|
1181
|
+
if (showNumbers()) {
|
|
1182
|
+
buildItems(currentPage, currentPages).forEach((item) => {
|
|
1183
|
+
if (item.type === "ellipsis") {
|
|
1184
|
+
const ellipsisCtx = { ...ctx, kind: "ellipsis" };
|
|
1185
|
+
const ellipsisNodes = renderNamedSlot("ellipsis", "…", ellipsisCtx);
|
|
1186
|
+
pagesGroup.appendChild(_.span({ class: "jss-pagination-ellipsis", "aria-hidden": "true" }, ...ellipsisNodes));
|
|
1187
|
+
return;
|
|
1188
|
+
}
|
|
1189
|
+
const itemPage = item.page;
|
|
1190
|
+
const itemCtx = { ...ctx, page: itemPage, value: itemPage, item, active: itemPage === currentPage };
|
|
1191
|
+
pagesGroup.appendChild(UI.Btn({
|
|
1192
|
+
class: uiClass([
|
|
1193
|
+
"jss-pagination-btn",
|
|
1194
|
+
"jss-pagination-page-btn",
|
|
1195
|
+
uiWhen(itemPage === currentPage, "active")
|
|
1196
|
+
]),
|
|
1197
|
+
size: props.itemSize || props.size || (props.dense ? "sm" : "md"),
|
|
1198
|
+
color: itemPage === currentPage ? (props.color || props.state || "primary") : null,
|
|
1199
|
+
outline: itemPage === currentPage ? false : true,
|
|
1200
|
+
disabled: isDisabled(),
|
|
1201
|
+
"aria-current": itemPage === currentPage ? "page" : null,
|
|
1202
|
+
onClick: (event) => commitPage(itemPage, event)
|
|
1203
|
+
}, ...renderNamedSlot("page", String(itemPage), itemCtx, "item")));
|
|
1204
|
+
});
|
|
1205
|
+
}
|
|
1206
|
+
|
|
1207
|
+
if (showNext()) {
|
|
1208
|
+
endGroup.appendChild(createButton("next", "Next", (event) => commitPage(currentPage + 1, event), {
|
|
1209
|
+
disabled: currentPage >= currentPages
|
|
1210
|
+
}));
|
|
1211
|
+
}
|
|
1212
|
+
if (showLast()) {
|
|
1213
|
+
endGroup.appendChild(createButton("last", "»", (event) => commitPage(currentPages, event), {
|
|
1214
|
+
disabled: currentPage >= currentPages
|
|
1215
|
+
}));
|
|
1216
|
+
}
|
|
1217
|
+
|
|
1218
|
+
const endNodes = renderNamedSlot("end", props.end, ctx);
|
|
1219
|
+
if (endNodes.length) {
|
|
1220
|
+
endGroup.appendChild(_.div({ class: "jss-pagination-extra jss-pagination-extra-end" }, ...endNodes));
|
|
1221
|
+
}
|
|
1222
|
+
|
|
1223
|
+
let summaryText = `Pagina ${currentPage} di ${currentPages}`;
|
|
1224
|
+
if (ctx.total != null && ctx.pageSize) {
|
|
1225
|
+
summaryText = ctx.total === 0
|
|
1226
|
+
? "0 risultati"
|
|
1227
|
+
: `${ctx.from}-${ctx.to} di ${ctx.total}`;
|
|
1228
|
+
}
|
|
1229
|
+
renderInto(summary, renderNamedSlot("summary", summaryText, ctx, "label"));
|
|
1230
|
+
};
|
|
1231
|
+
const commitPage = (page, event = null) => {
|
|
1232
|
+
const pages = getPages();
|
|
1233
|
+
const nextPage = clampPage(page, pages);
|
|
1234
|
+
if (nextPage === currentPage && pages === currentPages) return nextPage;
|
|
1235
|
+
render(nextPage, pages);
|
|
1236
|
+
if (model && model.get() !== nextPage) model.set(nextPage);
|
|
1237
|
+
props.onChange?.(nextPage, getCtx(nextPage, pages), event);
|
|
1238
|
+
props.onPageChange?.(nextPage, getCtx(nextPage, pages), event);
|
|
1239
|
+
return nextPage;
|
|
1240
|
+
};
|
|
1241
|
+
|
|
1242
|
+
app.reactive.effect(() => {
|
|
1243
|
+
const pages = getPages();
|
|
1244
|
+
const requested = getRequestedPage();
|
|
1245
|
+
const nextPage = clampPage(requested, pages);
|
|
1246
|
+
render(nextPage, pages);
|
|
1247
|
+
if (model && requested !== nextPage) model.set(nextPage);
|
|
1248
|
+
}, "UI.Pagination:sync");
|
|
1249
|
+
|
|
1250
|
+
wrap.getPage = () => currentPage;
|
|
1251
|
+
wrap.getPages = () => currentPages;
|
|
1252
|
+
wrap.setPage = (page) => commitPage(page);
|
|
1253
|
+
wrap.prev = () => commitPage(currentPage - 1);
|
|
1254
|
+
wrap.next = () => commitPage(currentPage + 1);
|
|
1255
|
+
wrap.first = () => commitPage(1);
|
|
1256
|
+
wrap.last = () => commitPage(currentPages);
|
|
1257
|
+
setPropertyProps(wrap, props);
|
|
1258
|
+
return wrap;
|
|
1259
|
+
};
|
|
1260
|
+
|
|
1261
|
+
// Esempio: JSswift.ui.Pagination({ total: 120, pageSize: 12, model: [get,set], showEdges: true })
|
|
1262
|
+
|