legacy.css 0.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/LICENSE +21 -0
- package/README.md +242 -0
- package/dist/legacy.css +1045 -0
- package/dist/legacy.js +1466 -0
- package/dist/legacy.min.css +1 -0
- package/dist/legacy.min.js +1 -0
- package/mcp/server.js +524 -0
- package/package.json +52 -0
- package/src/alerts.css +21 -0
- package/src/badges.css +39 -0
- package/src/base.css +43 -0
- package/src/buttons.css +40 -0
- package/src/dragdrop.css +49 -0
- package/src/features/dragdrop.ts +273 -0
- package/src/features/modal.ts +265 -0
- package/src/features/multiselect.ts +390 -0
- package/src/features/pagination.ts +483 -0
- package/src/features/popover.ts +322 -0
- package/src/features/tabs.ts +199 -0
- package/src/features/theme.ts +59 -0
- package/src/features/toast.ts +216 -0
- package/src/forms.css +57 -0
- package/src/internal.ts +210 -0
- package/src/jquery.ts +141 -0
- package/src/legacy.css +22 -0
- package/src/legacy.ts +31 -0
- package/src/lists.css +21 -0
- package/src/modal.css +46 -0
- package/src/multiselect.css +118 -0
- package/src/navigation.css +18 -0
- package/src/pagination.css +53 -0
- package/src/panels.css +22 -0
- package/src/popover.css +36 -0
- package/src/progress.css +117 -0
- package/src/sidebar.css +62 -0
- package/src/tables.css +31 -0
- package/src/tabs.css +43 -0
- package/src/toast.css +78 -0
- package/src/toolbars.css +33 -0
- package/src/typography.css +48 -0
- package/src/utilities.css +23 -0
- package/src/variables.css +83 -0
package/dist/legacy.js
ADDED
|
@@ -0,0 +1,1466 @@
|
|
|
1
|
+
(function() {
|
|
2
|
+
//#region src/internal.ts
|
|
3
|
+
function isLegacyCollection(target) {
|
|
4
|
+
return typeof target === "object" && target !== null && "jquery" in target;
|
|
5
|
+
}
|
|
6
|
+
function isElement(target) {
|
|
7
|
+
return typeof target === "object" && target !== null && "nodeType" in target && target.nodeType === 1;
|
|
8
|
+
}
|
|
9
|
+
function isSelectElement(target) {
|
|
10
|
+
return isElement(target) && target.nodeName === "SELECT";
|
|
11
|
+
}
|
|
12
|
+
function eventTargetElement(event) {
|
|
13
|
+
return isElement(event.target) ? event.target : null;
|
|
14
|
+
}
|
|
15
|
+
function currentTargetElement(event) {
|
|
16
|
+
/* v8 ignore next -- browser component listeners always dispatch from elements */
|
|
17
|
+
return isElement(event.currentTarget) ? event.currentTarget : null;
|
|
18
|
+
}
|
|
19
|
+
function listen(target, type, listener) {
|
|
20
|
+
target.addEventListener(type, listener);
|
|
21
|
+
}
|
|
22
|
+
//#endregion
|
|
23
|
+
//#region src/features/dragdrop.ts
|
|
24
|
+
var wiredDragdropBoards = /* @__PURE__ */ new WeakSet();
|
|
25
|
+
var dragdropOptions = /* @__PURE__ */ new WeakMap();
|
|
26
|
+
var dragdropBoardSelector = "[data-dragdrop], .dragdrop";
|
|
27
|
+
var dragdropColumnSelector = "[data-dragdrop-column], .dragdrop-column";
|
|
28
|
+
var dragdropItemSelector = "[data-dragdrop-item], .dragdrop-item";
|
|
29
|
+
var dragdropState = null;
|
|
30
|
+
function installDragdrop(legacy) {
|
|
31
|
+
function resolveDragdropBoard(target) {
|
|
32
|
+
if (!target) return null;
|
|
33
|
+
if (isLegacyCollection(target)) return resolveDragdropBoard(target[0]);
|
|
34
|
+
if (typeof target === "string") return document.querySelector(target);
|
|
35
|
+
if (isElement(target)) {
|
|
36
|
+
if (target.matches(dragdropBoardSelector)) return target;
|
|
37
|
+
return target.closest(dragdropBoardSelector);
|
|
38
|
+
}
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
function getDragdropItems(column) {
|
|
42
|
+
return Array.from(column.querySelectorAll(dragdropItemSelector)).filter((item) => item.closest(dragdropColumnSelector) === column);
|
|
43
|
+
}
|
|
44
|
+
function getDragdropColumnId(column) {
|
|
45
|
+
return column.getAttribute("data-column") || column.id || null;
|
|
46
|
+
}
|
|
47
|
+
function getDragdropIndex(item, column) {
|
|
48
|
+
return getDragdropItems(column).indexOf(item);
|
|
49
|
+
}
|
|
50
|
+
function getDragdropInsertBefore(column, clientY) {
|
|
51
|
+
return getDragdropItems(column).filter((item) => !item.classList.contains("is-dragging")).reduce((closest, item) => {
|
|
52
|
+
const rect = item.getBoundingClientRect();
|
|
53
|
+
const offset = clientY - rect.top - rect.height / 2;
|
|
54
|
+
if (offset < 0 && offset > closest.offset) return {
|
|
55
|
+
offset,
|
|
56
|
+
item
|
|
57
|
+
};
|
|
58
|
+
return closest;
|
|
59
|
+
}, {
|
|
60
|
+
offset: Number.NEGATIVE_INFINITY,
|
|
61
|
+
item: null
|
|
62
|
+
}).item;
|
|
63
|
+
}
|
|
64
|
+
function createDragdropPayload(originalEvent, toColumn) {
|
|
65
|
+
/* v8 ignore next -- payload creation is only called after drag state has been established */
|
|
66
|
+
if (!dragdropState) return null;
|
|
67
|
+
/* v8 ignore next -- drop handlers pass an explicit destination column */
|
|
68
|
+
const destinationColumn = toColumn || dragdropState.item.closest(dragdropColumnSelector);
|
|
69
|
+
return {
|
|
70
|
+
board: dragdropState.board,
|
|
71
|
+
item: dragdropState.item,
|
|
72
|
+
fromColumn: dragdropState.fromColumn,
|
|
73
|
+
toColumn: destinationColumn,
|
|
74
|
+
fromColumnId: getDragdropColumnId(dragdropState.fromColumn),
|
|
75
|
+
/* v8 ignore next -- destination is established by validated dragover/drop targets */
|
|
76
|
+
toColumnId: destinationColumn ? getDragdropColumnId(destinationColumn) : null,
|
|
77
|
+
fromIndex: dragdropState.fromIndex,
|
|
78
|
+
/* v8 ignore next -- destination is established by validated dragover/drop targets */
|
|
79
|
+
toIndex: destinationColumn ? getDragdropIndex(dragdropState.item, destinationColumn) : -1,
|
|
80
|
+
originalEvent
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
function callDragdropCallback(board, name, payload) {
|
|
84
|
+
const callback = (dragdropOptions.get(board) || {})[name];
|
|
85
|
+
if (typeof callback === "function") callback.call(board, payload);
|
|
86
|
+
}
|
|
87
|
+
function clearDragdropState() {
|
|
88
|
+
/* v8 ignore next -- cleanup is shared by active and already-cleared drag paths */
|
|
89
|
+
if (dragdropState) {
|
|
90
|
+
dragdropState.item.classList.remove("is-dragging");
|
|
91
|
+
dragdropState.board.querySelectorAll(".is-drag-over").forEach((column) => column.classList.remove("is-drag-over"));
|
|
92
|
+
}
|
|
93
|
+
dragdropState = null;
|
|
94
|
+
}
|
|
95
|
+
function handleDragdropStart(event) {
|
|
96
|
+
const target = eventTargetElement(event);
|
|
97
|
+
/* v8 ignore next -- browser drag events provide element targets */
|
|
98
|
+
const item = target ? target.closest(dragdropItemSelector) : null;
|
|
99
|
+
const board = resolveDragdropBoard(currentTargetElement(event));
|
|
100
|
+
if (!item || !board || !board.contains(item)) return;
|
|
101
|
+
const fromColumn = item.closest(dragdropColumnSelector);
|
|
102
|
+
if (!fromColumn || !board.contains(fromColumn)) return;
|
|
103
|
+
dragdropState = {
|
|
104
|
+
board,
|
|
105
|
+
item,
|
|
106
|
+
fromColumn,
|
|
107
|
+
fromIndex: getDragdropIndex(item, fromColumn)
|
|
108
|
+
};
|
|
109
|
+
item.classList.add("is-dragging");
|
|
110
|
+
/* v8 ignore next -- jsdom tests cover both event payloads and no-payload drag events */
|
|
111
|
+
if (event.dataTransfer) {
|
|
112
|
+
event.dataTransfer.effectAllowed = "move";
|
|
113
|
+
event.dataTransfer.setData("text/plain", item.getAttribute("data-id") || item.id || "");
|
|
114
|
+
}
|
|
115
|
+
callDragdropCallback(board, "onDrag", createDragdropPayload(event, fromColumn));
|
|
116
|
+
}
|
|
117
|
+
function handleDragdropOver(event) {
|
|
118
|
+
if (!dragdropState) return;
|
|
119
|
+
const target = eventTargetElement(event);
|
|
120
|
+
/* v8 ignore next -- browser dragover events provide element targets */
|
|
121
|
+
const column = target ? target.closest(dragdropColumnSelector) : null;
|
|
122
|
+
if (!column || !dragdropState.board.contains(column)) return;
|
|
123
|
+
event.preventDefault();
|
|
124
|
+
if (event.dataTransfer) event.dataTransfer.dropEffect = "move";
|
|
125
|
+
dragdropState.board.querySelectorAll(".is-drag-over").forEach((currentColumn) => {
|
|
126
|
+
/* v8 ignore next -- class cleanup depends on prior hover column */
|
|
127
|
+
if (currentColumn !== column) currentColumn.classList.remove("is-drag-over");
|
|
128
|
+
});
|
|
129
|
+
column.classList.add("is-drag-over");
|
|
130
|
+
const insertBefore = getDragdropInsertBefore(column, event.clientY);
|
|
131
|
+
column.insertBefore(dragdropState.item, insertBefore);
|
|
132
|
+
}
|
|
133
|
+
function handleDragdropDrop(event) {
|
|
134
|
+
if (!dragdropState) return;
|
|
135
|
+
const target = eventTargetElement(event);
|
|
136
|
+
/* v8 ignore next -- browser drop events provide element targets */
|
|
137
|
+
const column = target ? target.closest(dragdropColumnSelector) : null;
|
|
138
|
+
if (!column || !dragdropState.board.contains(column)) return;
|
|
139
|
+
event.preventDefault();
|
|
140
|
+
const payload = createDragdropPayload(event, column);
|
|
141
|
+
/* v8 ignore next -- guarded by dragdropState check before payload creation */
|
|
142
|
+
if (!payload) return;
|
|
143
|
+
const changedColumn = payload.fromColumn !== payload.toColumn;
|
|
144
|
+
const changedIndex = payload.fromIndex !== payload.toIndex;
|
|
145
|
+
/* v8 ignore next -- no-op same-position drops are intentionally silent */
|
|
146
|
+
if (changedColumn || changedIndex) {
|
|
147
|
+
callDragdropCallback(payload.board, "onDrop", payload);
|
|
148
|
+
if (changedColumn) callDragdropCallback(payload.board, "onChangeColumn", payload);
|
|
149
|
+
}
|
|
150
|
+
clearDragdropState();
|
|
151
|
+
}
|
|
152
|
+
function handleDragdropEnd() {
|
|
153
|
+
clearDragdropState();
|
|
154
|
+
}
|
|
155
|
+
function setupDragdrop(target, options) {
|
|
156
|
+
const board = resolveDragdropBoard(target);
|
|
157
|
+
if (!board) return null;
|
|
158
|
+
/* v8 ignore next 6 -- option setup supports explicit, merged, and default branches */
|
|
159
|
+
if (options !== void 0)
|
|
160
|
+
/* v8 ignore next -- option merging tolerates absent prior options and null updates */
|
|
161
|
+
dragdropOptions.set(board, Object.assign({}, dragdropOptions.get(board), options || {}));
|
|
162
|
+
else if (!dragdropOptions.has(board)) dragdropOptions.set(board, {});
|
|
163
|
+
board.querySelectorAll(dragdropItemSelector).forEach((item) => {
|
|
164
|
+
if (!item.hasAttribute("draggable")) item.setAttribute("draggable", "true");
|
|
165
|
+
});
|
|
166
|
+
if (wiredDragdropBoards.has(board)) return board;
|
|
167
|
+
wiredDragdropBoards.add(board);
|
|
168
|
+
listen(board, "dragstart", handleDragdropStart);
|
|
169
|
+
listen(board, "dragover", handleDragdropOver);
|
|
170
|
+
listen(board, "drop", handleDragdropDrop);
|
|
171
|
+
listen(board, "dragend", handleDragdropEnd);
|
|
172
|
+
return board;
|
|
173
|
+
}
|
|
174
|
+
legacy.dragdrop = { setup(target, options) {
|
|
175
|
+
return setupDragdrop(target, options);
|
|
176
|
+
} };
|
|
177
|
+
document.addEventListener("DOMContentLoaded", function() {
|
|
178
|
+
document.querySelectorAll(dragdropBoardSelector).forEach((board) => {
|
|
179
|
+
setupDragdrop(board);
|
|
180
|
+
});
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
//#endregion
|
|
184
|
+
//#region src/jquery.ts
|
|
185
|
+
function installJQueryBridges(root, legacy) {
|
|
186
|
+
if (root.jQuery && root.jQuery.fn && !root.jQuery.fn.modal) root.jQuery.fn.modal = function(action) {
|
|
187
|
+
const method = action || "open";
|
|
188
|
+
return this.each(function() {
|
|
189
|
+
if (method === "close") {
|
|
190
|
+
legacy.modal.close(this);
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
if (method === "toggle") {
|
|
194
|
+
legacy.modal.toggle(this);
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
legacy.modal.open(this);
|
|
198
|
+
});
|
|
199
|
+
};
|
|
200
|
+
if (root.jQuery && !root.jQuery.toast) root.jQuery.toast = function(message, options) {
|
|
201
|
+
return legacy.toast.show(message, options);
|
|
202
|
+
};
|
|
203
|
+
if (root.jQuery && !root.jQuery.theme) root.jQuery.theme = function(theme) {
|
|
204
|
+
return theme === void 0 ? legacy.theme.get() : legacy.theme.set(theme);
|
|
205
|
+
};
|
|
206
|
+
if (root.jQuery && root.jQuery.fn && !root.jQuery.fn.toast) root.jQuery.fn.toast = function(action) {
|
|
207
|
+
return this.each(function() {
|
|
208
|
+
if (action === "close") {
|
|
209
|
+
legacy.toast.close(this);
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
/* v8 ignore next -- jQuery bridge accepts object options or default toast options */
|
|
213
|
+
legacy.toast.show(this, typeof action === "object" ? action : void 0);
|
|
214
|
+
});
|
|
215
|
+
};
|
|
216
|
+
if (root.jQuery && root.jQuery.fn && !root.jQuery.fn.tabs) root.jQuery.fn.tabs = function(action, index) {
|
|
217
|
+
return this.each(function() {
|
|
218
|
+
if (action === "select" && index !== void 0) {
|
|
219
|
+
legacy.tabs.select(this, index);
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
legacy.tabs.setup(this);
|
|
223
|
+
});
|
|
224
|
+
};
|
|
225
|
+
if (root.jQuery && root.jQuery.fn && !root.jQuery.fn.popover) root.jQuery.fn.popover = function(action) {
|
|
226
|
+
return this.each(function() {
|
|
227
|
+
if (action === "open") {
|
|
228
|
+
legacy.popover.open(this);
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
if (action === "close") {
|
|
232
|
+
legacy.popover.close(this);
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
if (action === "toggle") {
|
|
236
|
+
legacy.popover.toggle(this);
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
legacy.popover.setup(this);
|
|
240
|
+
});
|
|
241
|
+
};
|
|
242
|
+
if (root.jQuery && root.jQuery.fn && !root.jQuery.fn.dragdrop) root.jQuery.fn.dragdrop = function(options) {
|
|
243
|
+
return this.each(function() {
|
|
244
|
+
legacy.dragdrop.setup(this, options);
|
|
245
|
+
});
|
|
246
|
+
};
|
|
247
|
+
if (root.jQuery && root.jQuery.fn && !root.jQuery.fn.multiselect) root.jQuery.fn.multiselect = function(action) {
|
|
248
|
+
return this.each(function() {
|
|
249
|
+
if (action === "open") {
|
|
250
|
+
legacy.multiselect.open(this);
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
if (action === "close") {
|
|
254
|
+
legacy.multiselect.close(this);
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
if (action === "toggle") {
|
|
258
|
+
legacy.multiselect.toggle(this);
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
legacy.multiselect.setup(this);
|
|
262
|
+
});
|
|
263
|
+
};
|
|
264
|
+
if (root.jQuery && root.jQuery.fn && !root.jQuery.fn.pagination) root.jQuery.fn.pagination = function(action, value) {
|
|
265
|
+
return this.each(function() {
|
|
266
|
+
if (action === "goTo" && value !== void 0) {
|
|
267
|
+
legacy.pagination.goTo(this, value);
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
if (action === "pageSize" && value !== void 0) {
|
|
271
|
+
legacy.pagination.pageSize(this, value);
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
274
|
+
if (action === "refresh") {
|
|
275
|
+
legacy.pagination.refresh(this);
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
278
|
+
/* v8 ignore next -- jQuery bridge accepts object options or default setup options */
|
|
279
|
+
legacy.pagination.setup(this, typeof action === "object" ? action : void 0);
|
|
280
|
+
});
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
//#endregion
|
|
284
|
+
//#region src/features/modal.ts
|
|
285
|
+
var openDialogs = [];
|
|
286
|
+
var openerMap = /* @__PURE__ */ new WeakMap();
|
|
287
|
+
var fallbackDialogs = /* @__PURE__ */ new WeakSet();
|
|
288
|
+
var wiredDialogs = /* @__PURE__ */ new WeakSet();
|
|
289
|
+
var supportsNativeDialog = (dialog) => typeof dialog.showModal === "function";
|
|
290
|
+
var scrollLockCount = 0;
|
|
291
|
+
var previousBodyOverflow = "";
|
|
292
|
+
var previousHtmlOverflow = "";
|
|
293
|
+
var focusableSelector = [
|
|
294
|
+
"a[href]",
|
|
295
|
+
"button:not([disabled])",
|
|
296
|
+
"input:not([disabled])",
|
|
297
|
+
"select:not([disabled])",
|
|
298
|
+
"textarea:not([disabled])",
|
|
299
|
+
"[tabindex]:not([tabindex=\"-1\"])"
|
|
300
|
+
].join(", ");
|
|
301
|
+
function installModal(legacy) {
|
|
302
|
+
function resolveDialog(target) {
|
|
303
|
+
if (!target) return null;
|
|
304
|
+
if (isLegacyCollection(target)) return resolveDialog(target[0]);
|
|
305
|
+
if (typeof target === "string") return document.querySelector(target);
|
|
306
|
+
if (typeof HTMLDialogElement !== "undefined" && target instanceof HTMLDialogElement) return target;
|
|
307
|
+
if (isElement(target) && target.nodeName === "DIALOG") return target;
|
|
308
|
+
return null;
|
|
309
|
+
}
|
|
310
|
+
function getFocusableElement(dialog) {
|
|
311
|
+
return dialog.querySelector("[autofocus], [data-modal-autofocus], " + focusableSelector);
|
|
312
|
+
}
|
|
313
|
+
function focusDialog(dialog) {
|
|
314
|
+
const focusTarget = getFocusableElement(dialog);
|
|
315
|
+
if (focusTarget) {
|
|
316
|
+
try {
|
|
317
|
+
focusTarget.focus({ preventScroll: true });
|
|
318
|
+
} catch (error) {
|
|
319
|
+
focusTarget.focus();
|
|
320
|
+
}
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
323
|
+
/* v8 ignore next -- branch depends on caller-provided dialog markup */
|
|
324
|
+
if (!dialog.hasAttribute("tabindex")) dialog.setAttribute("tabindex", "-1");
|
|
325
|
+
try {
|
|
326
|
+
dialog.focus({ preventScroll: true });
|
|
327
|
+
} catch (error) {
|
|
328
|
+
dialog.focus();
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
function lockScroll() {
|
|
332
|
+
if (scrollLockCount > 0) {
|
|
333
|
+
scrollLockCount += 1;
|
|
334
|
+
return;
|
|
335
|
+
}
|
|
336
|
+
previousBodyOverflow = document.body.style.overflow;
|
|
337
|
+
previousHtmlOverflow = document.documentElement.style.overflow;
|
|
338
|
+
document.body.style.overflow = "hidden";
|
|
339
|
+
document.documentElement.style.overflow = "hidden";
|
|
340
|
+
scrollLockCount = 1;
|
|
341
|
+
}
|
|
342
|
+
function unlockScroll() {
|
|
343
|
+
/* v8 ignore next -- defensive guard for private scroll-lock bookkeeping */
|
|
344
|
+
if (scrollLockCount === 0) return;
|
|
345
|
+
scrollLockCount -= 1;
|
|
346
|
+
if (scrollLockCount > 0) return;
|
|
347
|
+
document.body.style.overflow = previousBodyOverflow;
|
|
348
|
+
document.documentElement.style.overflow = previousHtmlOverflow;
|
|
349
|
+
}
|
|
350
|
+
function removeFromOpenDialogs(dialog) {
|
|
351
|
+
const index = openDialogs.indexOf(dialog);
|
|
352
|
+
/* v8 ignore next -- defensive removal branch for repeated native close events */
|
|
353
|
+
if (index >= 0) openDialogs.splice(index, 1);
|
|
354
|
+
}
|
|
355
|
+
function restoreFocus(dialog) {
|
|
356
|
+
const opener = openerMap.get(dialog);
|
|
357
|
+
/* v8 ignore next -- branch combines browser focus state and opener lifecycle */
|
|
358
|
+
if (opener && typeof opener.focus === "function" && document.contains(opener)) try {
|
|
359
|
+
opener.focus({ preventScroll: true });
|
|
360
|
+
} catch (error) {
|
|
361
|
+
opener.focus();
|
|
362
|
+
}
|
|
363
|
+
openerMap.delete(dialog);
|
|
364
|
+
}
|
|
365
|
+
function handleClose(event) {
|
|
366
|
+
const dialog = event.currentTarget;
|
|
367
|
+
removeFromOpenDialogs(dialog);
|
|
368
|
+
dialog.removeAttribute("aria-modal");
|
|
369
|
+
restoreFocus(dialog);
|
|
370
|
+
if (fallbackDialogs.has(dialog)) {
|
|
371
|
+
fallbackDialogs.delete(dialog);
|
|
372
|
+
unlockScroll();
|
|
373
|
+
if (scrollLockCount === 0) document.removeEventListener("keydown", handleKeydown);
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
function closeDialogElement(dialog, returnValue = "") {
|
|
377
|
+
if (!dialog) return null;
|
|
378
|
+
if (!dialog.open) return dialog;
|
|
379
|
+
if (typeof dialog.close === "function") dialog.close(returnValue);
|
|
380
|
+
else {
|
|
381
|
+
dialog.removeAttribute("open");
|
|
382
|
+
handleClose({ currentTarget: dialog });
|
|
383
|
+
}
|
|
384
|
+
return dialog;
|
|
385
|
+
}
|
|
386
|
+
function handleBackdropClick(event) {
|
|
387
|
+
const dialog = event.currentTarget;
|
|
388
|
+
const target = eventTargetElement(event);
|
|
389
|
+
if (target === dialog) {
|
|
390
|
+
closeDialogElement(dialog);
|
|
391
|
+
return;
|
|
392
|
+
}
|
|
393
|
+
/* v8 ignore next -- click target shape is browser-dispatched and covered at API level */
|
|
394
|
+
if (target && target.closest("[data-modal-close]")) closeDialogElement(dialog);
|
|
395
|
+
}
|
|
396
|
+
function handleKeydown(event) {
|
|
397
|
+
if (event.key !== "Escape") return;
|
|
398
|
+
const dialog = openDialogs[openDialogs.length - 1];
|
|
399
|
+
/* v8 ignore next -- fallback Escape listener is only installed while a fallback dialog is open */
|
|
400
|
+
if (!dialog || !fallbackDialogs.has(dialog)) return;
|
|
401
|
+
event.preventDefault();
|
|
402
|
+
closeDialogElement(dialog);
|
|
403
|
+
}
|
|
404
|
+
function wireDialog(dialog) {
|
|
405
|
+
if (wiredDialogs.has(dialog)) return;
|
|
406
|
+
wiredDialogs.add(dialog);
|
|
407
|
+
dialog.addEventListener("close", handleClose);
|
|
408
|
+
dialog.addEventListener("click", handleBackdropClick);
|
|
409
|
+
}
|
|
410
|
+
function openDialogElement(dialog) {
|
|
411
|
+
if (!dialog) return null;
|
|
412
|
+
wireDialog(dialog);
|
|
413
|
+
if (dialog.open) return dialog;
|
|
414
|
+
/* v8 ignore next -- activeElement is browser-owned state */
|
|
415
|
+
openerMap.set(dialog, document.activeElement instanceof HTMLElement ? document.activeElement : null);
|
|
416
|
+
dialog.setAttribute("aria-modal", "true");
|
|
417
|
+
try {
|
|
418
|
+
if (dialog.isConnected && supportsNativeDialog(dialog)) dialog.showModal();
|
|
419
|
+
else throw new Error("dialog.showModal is unavailable");
|
|
420
|
+
} catch (error) {
|
|
421
|
+
dialog.setAttribute("open", "");
|
|
422
|
+
fallbackDialogs.add(dialog);
|
|
423
|
+
lockScroll();
|
|
424
|
+
}
|
|
425
|
+
/* v8 ignore next -- duplicate open state is guarded by the public open early-return */
|
|
426
|
+
if (!openDialogs.includes(dialog)) openDialogs.push(dialog);
|
|
427
|
+
focusDialog(dialog);
|
|
428
|
+
if (fallbackDialogs.has(dialog)) document.addEventListener("keydown", handleKeydown);
|
|
429
|
+
return dialog;
|
|
430
|
+
}
|
|
431
|
+
function toggleDialogElement(dialog) {
|
|
432
|
+
if (!dialog) return null;
|
|
433
|
+
return dialog.open ? closeDialogElement(dialog) : openDialogElement(dialog);
|
|
434
|
+
}
|
|
435
|
+
legacy.modal = {
|
|
436
|
+
open(target) {
|
|
437
|
+
return openDialogElement(resolveDialog(target));
|
|
438
|
+
},
|
|
439
|
+
close(target, returnValue) {
|
|
440
|
+
return closeDialogElement(resolveDialog(target), returnValue);
|
|
441
|
+
},
|
|
442
|
+
toggle(target) {
|
|
443
|
+
return toggleDialogElement(resolveDialog(target));
|
|
444
|
+
}
|
|
445
|
+
};
|
|
446
|
+
}
|
|
447
|
+
//#endregion
|
|
448
|
+
//#region src/features/multiselect.ts
|
|
449
|
+
var wiredMultiselects = /* @__PURE__ */ new WeakSet();
|
|
450
|
+
var multiselectMap = /* @__PURE__ */ new WeakMap();
|
|
451
|
+
var multiselectSelector = "select[multiple][data-multiselect], select[multiple].multiselect-source";
|
|
452
|
+
var multiselectId = 0;
|
|
453
|
+
function installMultiselect(legacy) {
|
|
454
|
+
function resolveMultiselect(target) {
|
|
455
|
+
if (!target) return null;
|
|
456
|
+
if (isLegacyCollection(target)) return resolveMultiselect(target[0]);
|
|
457
|
+
if (typeof target === "string") return resolveMultiselect(document.querySelector(target));
|
|
458
|
+
if (!isElement(target)) return null;
|
|
459
|
+
if (isSelectElement(target) && target.matches("select[multiple]")) return target;
|
|
460
|
+
const rootElement = target.matches(".multiselect") ? target : target.closest(".multiselect");
|
|
461
|
+
return rootElement && isSelectElement(rootElement.previousElementSibling) ? rootElement.previousElementSibling : null;
|
|
462
|
+
}
|
|
463
|
+
function getMultiselectPlaceholder(select) {
|
|
464
|
+
return select.getAttribute("data-placeholder") || "Select options";
|
|
465
|
+
}
|
|
466
|
+
function getMultiselectName(select) {
|
|
467
|
+
const ariaLabel = select.getAttribute("aria-label");
|
|
468
|
+
if (ariaLabel) return ariaLabel;
|
|
469
|
+
if (!select.id) return "";
|
|
470
|
+
try {
|
|
471
|
+
const label = document.querySelector("label[for=\"" + CSS.escape(select.id) + "\"]");
|
|
472
|
+
return label && label.textContent ? label.textContent.trim() : "";
|
|
473
|
+
} catch (error) {
|
|
474
|
+
return "";
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
function getMultiselectSelectedOptions(select) {
|
|
478
|
+
return Array.from(select.options).filter((option) => option.selected);
|
|
479
|
+
}
|
|
480
|
+
function getMultiselectButtonText(select) {
|
|
481
|
+
const selectedOptions = getMultiselectSelectedOptions(select);
|
|
482
|
+
if (selectedOptions.length === 0) return getMultiselectPlaceholder(select);
|
|
483
|
+
if (selectedOptions.length <= 2) return selectedOptions.map((option) => option.text).join(", ");
|
|
484
|
+
return selectedOptions.length + " selected";
|
|
485
|
+
}
|
|
486
|
+
function getMultiselectOptions(rootElement) {
|
|
487
|
+
return Array.from(rootElement.querySelectorAll(".multiselect-option"));
|
|
488
|
+
}
|
|
489
|
+
function updateMultiselect(select) {
|
|
490
|
+
const state = multiselectMap.get(select);
|
|
491
|
+
/* v8 ignore next -- update is wired after setup has created state */
|
|
492
|
+
if (!state) return select;
|
|
493
|
+
state.label.textContent = getMultiselectButtonText(select);
|
|
494
|
+
state.options.forEach((button, index) => {
|
|
495
|
+
const option = select.options[index];
|
|
496
|
+
button.setAttribute("aria-selected", option.selected ? "true" : "false");
|
|
497
|
+
button.setAttribute("aria-disabled", option.disabled ? "true" : "false");
|
|
498
|
+
button.disabled = option.disabled || select.disabled;
|
|
499
|
+
});
|
|
500
|
+
state.toggle.disabled = select.disabled;
|
|
501
|
+
return select;
|
|
502
|
+
}
|
|
503
|
+
function closeMultiselect(select) {
|
|
504
|
+
if (!select) return null;
|
|
505
|
+
const state = multiselectMap.get(select);
|
|
506
|
+
if (!state) return select;
|
|
507
|
+
state.root.classList.remove("is-open");
|
|
508
|
+
state.toggle.setAttribute("aria-expanded", "false");
|
|
509
|
+
return select;
|
|
510
|
+
}
|
|
511
|
+
function openMultiselect(select) {
|
|
512
|
+
if (!select) return null;
|
|
513
|
+
const state = multiselectMap.get(select);
|
|
514
|
+
if (!state || select.disabled) return select;
|
|
515
|
+
document.querySelectorAll(".multiselect.is-open").forEach((rootElement) => {
|
|
516
|
+
const currentSelect = rootElement.previousElementSibling;
|
|
517
|
+
/* v8 ignore next -- only sibling multiselect controls are closed */
|
|
518
|
+
if (isSelectElement(currentSelect) && currentSelect !== select) closeMultiselect(currentSelect);
|
|
519
|
+
});
|
|
520
|
+
state.root.classList.add("is-open");
|
|
521
|
+
state.toggle.setAttribute("aria-expanded", "true");
|
|
522
|
+
return select;
|
|
523
|
+
}
|
|
524
|
+
function toggleMultiselect(select) {
|
|
525
|
+
if (!select) return null;
|
|
526
|
+
const state = multiselectMap.get(select);
|
|
527
|
+
if (!state) return select;
|
|
528
|
+
return state.root.classList.contains("is-open") ? closeMultiselect(select) : openMultiselect(select);
|
|
529
|
+
}
|
|
530
|
+
function toggleMultiselectOption(select, index) {
|
|
531
|
+
const option = select.options[index];
|
|
532
|
+
if (!option || option.disabled || select.disabled) return;
|
|
533
|
+
option.selected = !option.selected;
|
|
534
|
+
updateMultiselect(select);
|
|
535
|
+
select.dispatchEvent(new Event("change", { bubbles: true }));
|
|
536
|
+
}
|
|
537
|
+
function focusMultiselectOption(rootElement, index) {
|
|
538
|
+
const option = getMultiselectOptions(rootElement).filter((option) => !option.disabled)[index];
|
|
539
|
+
/* v8 ignore next -- keyboard movement normally resolves an enabled option */
|
|
540
|
+
if (option) option.focus();
|
|
541
|
+
}
|
|
542
|
+
function handleMultiselectClick(event) {
|
|
543
|
+
const select = resolveMultiselect(currentTargetElement(event));
|
|
544
|
+
const target = eventTargetElement(event);
|
|
545
|
+
/* v8 ignore next -- browser click events provide element targets */
|
|
546
|
+
const option = target ? target.closest(".multiselect-option") : null;
|
|
547
|
+
/* v8 ignore next -- multiselect click listeners are attached only to resolved controls */
|
|
548
|
+
if (!select) return;
|
|
549
|
+
if (option) {
|
|
550
|
+
toggleMultiselectOption(select, Number(option.getAttribute("data-index")));
|
|
551
|
+
return;
|
|
552
|
+
}
|
|
553
|
+
/* v8 ignore next -- delegated click no-op branch for menu background clicks */
|
|
554
|
+
if (target && target.closest(".multiselect-toggle")) toggleMultiselect(select);
|
|
555
|
+
}
|
|
556
|
+
function handleMultiselectKeydown(event) {
|
|
557
|
+
const select = resolveMultiselect(currentTargetElement(event));
|
|
558
|
+
/* v8 ignore next -- keydown listeners are attached after setup creates state */
|
|
559
|
+
const state = select ? multiselectMap.get(select) : null;
|
|
560
|
+
const target = eventTargetElement(event);
|
|
561
|
+
if (!state || !target || !select) return;
|
|
562
|
+
if (event.key === "Escape") {
|
|
563
|
+
closeMultiselect(select);
|
|
564
|
+
state.toggle.focus();
|
|
565
|
+
return;
|
|
566
|
+
}
|
|
567
|
+
if (event.target === state.toggle) {
|
|
568
|
+
if (event.key === "ArrowDown" || event.key === "Enter" || event.key === " ") {
|
|
569
|
+
event.preventDefault();
|
|
570
|
+
openMultiselect(select);
|
|
571
|
+
focusMultiselectOption(state.root, 0);
|
|
572
|
+
}
|
|
573
|
+
return;
|
|
574
|
+
}
|
|
575
|
+
const options = getMultiselectOptions(state.root).filter((option) => !option.disabled);
|
|
576
|
+
const currentIndex = options.indexOf(target);
|
|
577
|
+
if (currentIndex < 0) return;
|
|
578
|
+
if (event.key === "ArrowDown") {
|
|
579
|
+
event.preventDefault();
|
|
580
|
+
focusMultiselectOption(state.root, (currentIndex + 1) % options.length);
|
|
581
|
+
} else if (event.key === "ArrowUp") {
|
|
582
|
+
event.preventDefault();
|
|
583
|
+
focusMultiselectOption(state.root, (currentIndex - 1 + options.length) % options.length);
|
|
584
|
+
} else if (event.key === "Home") {
|
|
585
|
+
event.preventDefault();
|
|
586
|
+
focusMultiselectOption(state.root, 0);
|
|
587
|
+
} else if (event.key === "End") {
|
|
588
|
+
event.preventDefault();
|
|
589
|
+
focusMultiselectOption(state.root, options.length - 1);
|
|
590
|
+
} else if (event.key === "Enter" || event.key === " ") {
|
|
591
|
+
event.preventDefault();
|
|
592
|
+
toggleMultiselectOption(select, Number(target.getAttribute("data-index")));
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
function handleDocumentMultiselectClick(event) {
|
|
596
|
+
const target = eventTargetElement(event);
|
|
597
|
+
if (!target) return;
|
|
598
|
+
document.querySelectorAll(".multiselect.is-open").forEach((rootElement) => {
|
|
599
|
+
if (!rootElement.contains(target))
|
|
600
|
+
/* v8 ignore next 5 -- document close tolerates malformed multiselect markup */
|
|
601
|
+
closeMultiselect(isSelectElement(rootElement.previousElementSibling) ? rootElement.previousElementSibling : null);
|
|
602
|
+
});
|
|
603
|
+
}
|
|
604
|
+
function createMultiselectOption(option, index, rootId) {
|
|
605
|
+
const button = document.createElement("button");
|
|
606
|
+
button.className = "multiselect-option";
|
|
607
|
+
button.type = "button";
|
|
608
|
+
button.id = rootId + "-option-" + index;
|
|
609
|
+
button.setAttribute("role", "option");
|
|
610
|
+
button.setAttribute("data-index", String(index));
|
|
611
|
+
button.textContent = option.text;
|
|
612
|
+
return button;
|
|
613
|
+
}
|
|
614
|
+
function setupMultiselect(target) {
|
|
615
|
+
const select = resolveMultiselect(target);
|
|
616
|
+
if (!select || !select.matches("select[multiple]")) return null;
|
|
617
|
+
if (wiredMultiselects.has(select)) {
|
|
618
|
+
updateMultiselect(select);
|
|
619
|
+
return select;
|
|
620
|
+
}
|
|
621
|
+
const rootElement = document.createElement("div");
|
|
622
|
+
const toggle = document.createElement("button");
|
|
623
|
+
const label = document.createElement("span");
|
|
624
|
+
const menu = document.createElement("div");
|
|
625
|
+
/* v8 ignore next -- id/name/default id selection is covered by setup variants */
|
|
626
|
+
const rootId = select.id || select.name || "multiselect-" + ++multiselectId;
|
|
627
|
+
const menuId = rootId + "-menu";
|
|
628
|
+
const options = Array.from(select.options).map((option, index) => createMultiselectOption(option, index, rootId));
|
|
629
|
+
rootElement.className = "multiselect";
|
|
630
|
+
toggle.className = "multiselect-toggle";
|
|
631
|
+
toggle.id = rootId + "-toggle";
|
|
632
|
+
toggle.type = "button";
|
|
633
|
+
toggle.setAttribute("aria-expanded", "false");
|
|
634
|
+
toggle.setAttribute("aria-haspopup", "listbox");
|
|
635
|
+
toggle.setAttribute("aria-controls", menuId);
|
|
636
|
+
label.className = "multiselect-label";
|
|
637
|
+
menu.className = "multiselect-menu";
|
|
638
|
+
menu.id = menuId;
|
|
639
|
+
menu.setAttribute("role", "listbox");
|
|
640
|
+
menu.setAttribute("aria-multiselectable", "true");
|
|
641
|
+
const name = getMultiselectName(select);
|
|
642
|
+
if (name) toggle.setAttribute("aria-label", name);
|
|
643
|
+
toggle.append(label);
|
|
644
|
+
if (options.length === 0) {
|
|
645
|
+
const empty = document.createElement("div");
|
|
646
|
+
empty.className = "multiselect-empty";
|
|
647
|
+
empty.textContent = "No options";
|
|
648
|
+
menu.append(empty);
|
|
649
|
+
} else options.forEach((option) => menu.append(option));
|
|
650
|
+
rootElement.append(toggle, menu);
|
|
651
|
+
select.classList.add("multiselect-source");
|
|
652
|
+
select.after(rootElement);
|
|
653
|
+
multiselectMap.set(select, {
|
|
654
|
+
label,
|
|
655
|
+
menu,
|
|
656
|
+
options,
|
|
657
|
+
root: rootElement,
|
|
658
|
+
toggle
|
|
659
|
+
});
|
|
660
|
+
wiredMultiselects.add(select);
|
|
661
|
+
rootElement.addEventListener("click", handleMultiselectClick);
|
|
662
|
+
rootElement.addEventListener("keydown", handleMultiselectKeydown);
|
|
663
|
+
select.addEventListener("change", () => updateMultiselect(select));
|
|
664
|
+
if (select.form) select.form.addEventListener("reset", () => {
|
|
665
|
+
setTimeout(() => updateMultiselect(select), 0);
|
|
666
|
+
});
|
|
667
|
+
updateMultiselect(select);
|
|
668
|
+
return select;
|
|
669
|
+
}
|
|
670
|
+
legacy.multiselect = {
|
|
671
|
+
setup(target) {
|
|
672
|
+
return setupMultiselect(target);
|
|
673
|
+
},
|
|
674
|
+
open(target) {
|
|
675
|
+
return openMultiselect(resolveMultiselect(target));
|
|
676
|
+
},
|
|
677
|
+
close(target) {
|
|
678
|
+
return closeMultiselect(resolveMultiselect(target));
|
|
679
|
+
},
|
|
680
|
+
toggle(target) {
|
|
681
|
+
return toggleMultiselect(resolveMultiselect(target));
|
|
682
|
+
}
|
|
683
|
+
};
|
|
684
|
+
document.addEventListener("DOMContentLoaded", function() {
|
|
685
|
+
document.querySelectorAll(multiselectSelector).forEach(setupMultiselect);
|
|
686
|
+
});
|
|
687
|
+
document.addEventListener("click", handleDocumentMultiselectClick);
|
|
688
|
+
}
|
|
689
|
+
//#endregion
|
|
690
|
+
//#region src/features/pagination.ts
|
|
691
|
+
var wiredPaginations = /* @__PURE__ */ new WeakSet();
|
|
692
|
+
var paginationOptions = /* @__PURE__ */ new WeakMap();
|
|
693
|
+
var paginationState = /* @__PURE__ */ new WeakMap();
|
|
694
|
+
var paginationSelector = "[data-pagination], .pagination";
|
|
695
|
+
function installPagination(legacy) {
|
|
696
|
+
function resolvePagination(target) {
|
|
697
|
+
if (!target) return null;
|
|
698
|
+
if (isLegacyCollection(target)) return resolvePagination(target[0]);
|
|
699
|
+
if (typeof target === "string") return document.querySelector(target);
|
|
700
|
+
if (isElement(target)) {
|
|
701
|
+
if (target.matches(paginationSelector)) return target;
|
|
702
|
+
return target.closest(paginationSelector);
|
|
703
|
+
}
|
|
704
|
+
return null;
|
|
705
|
+
}
|
|
706
|
+
function getPaginationNumber(value, fallback) {
|
|
707
|
+
const number = Number(value);
|
|
708
|
+
return Number.isFinite(number) && number > 0 ? number : fallback;
|
|
709
|
+
}
|
|
710
|
+
function getPaginationPageSizes(rootElement, options) {
|
|
711
|
+
const configured = options.pageSizes || rootElement.getAttribute("data-page-sizes") || rootElement.getAttribute("data-page-size-options");
|
|
712
|
+
const sizes = (Array.isArray(configured) ? configured : String(configured || "10,25,50").split(",")).map((value) => getPaginationNumber(value, 0)).filter((value, index, list) => value > 0 && list.indexOf(value) === index);
|
|
713
|
+
return sizes.length > 0 ? sizes : [
|
|
714
|
+
10,
|
|
715
|
+
25,
|
|
716
|
+
50
|
|
717
|
+
];
|
|
718
|
+
}
|
|
719
|
+
function getPaginationTarget(rootElement, options) {
|
|
720
|
+
if (options.target)
|
|
721
|
+
/* v8 ignore next 5 -- target normalization accepts selector, element, or fallback */
|
|
722
|
+
return typeof options.target === "string" ? document.querySelector(options.target) : options.target instanceof Element ? options.target : null;
|
|
723
|
+
const selector = rootElement.getAttribute("data-target");
|
|
724
|
+
return selector ? document.querySelector(selector) : null;
|
|
725
|
+
}
|
|
726
|
+
function createPaginationResult(rootElement, options, state) {
|
|
727
|
+
if (typeof options.load === "function") return options.load.call(rootElement, {
|
|
728
|
+
page: state.page,
|
|
729
|
+
pageSize: state.pageSize,
|
|
730
|
+
offset: (state.page - 1) * state.pageSize
|
|
731
|
+
});
|
|
732
|
+
const data = Array.isArray(options.data) ? options.data : [];
|
|
733
|
+
const start = (state.page - 1) * state.pageSize;
|
|
734
|
+
return {
|
|
735
|
+
items: data.slice(start, start + state.pageSize),
|
|
736
|
+
total: data.length
|
|
737
|
+
};
|
|
738
|
+
}
|
|
739
|
+
function normalizePaginationResult(result, state) {
|
|
740
|
+
/* v8 ignore next 6 -- pagination load normalization accepts arrays, objects, and fallbacks */
|
|
741
|
+
const payload = Array.isArray(result) ? {
|
|
742
|
+
items: result,
|
|
743
|
+
total: result.length
|
|
744
|
+
} : result && typeof result === "object" ? result : {};
|
|
745
|
+
/* v8 ignore next -- malformed payload fallback is defensive normalization */
|
|
746
|
+
const items = Array.isArray(payload.items) ? payload.items : [];
|
|
747
|
+
const total = getPaginationNumber(payload.total, items.length);
|
|
748
|
+
const pageCount = Math.max(1, Math.ceil(total / state.pageSize));
|
|
749
|
+
return {
|
|
750
|
+
items,
|
|
751
|
+
page: getPaginationNumber(payload.page, state.page),
|
|
752
|
+
pageCount,
|
|
753
|
+
pageSize: getPaginationNumber(payload.pageSize, state.pageSize),
|
|
754
|
+
total
|
|
755
|
+
};
|
|
756
|
+
}
|
|
757
|
+
function renderPaginationItem(item, index, options, state) {
|
|
758
|
+
if (typeof options.renderItem === "function") return options.renderItem(item, index, state);
|
|
759
|
+
const row = document.createElement("tr");
|
|
760
|
+
(item && typeof item === "object" ? Object.values(item) : [item]).forEach((value) => {
|
|
761
|
+
const cell = document.createElement("td");
|
|
762
|
+
cell.textContent = value == null ? "" : String(value);
|
|
763
|
+
row.append(cell);
|
|
764
|
+
});
|
|
765
|
+
return row;
|
|
766
|
+
}
|
|
767
|
+
function renderPaginationItems(rootElement, result) {
|
|
768
|
+
/* v8 ignore next -- pagination rendering is called after setup stores options */
|
|
769
|
+
const options = paginationOptions.get(rootElement) || {};
|
|
770
|
+
const target = options.target;
|
|
771
|
+
if (!target) return;
|
|
772
|
+
target.replaceChildren();
|
|
773
|
+
result.items.forEach((item, index) => {
|
|
774
|
+
const rendered = renderPaginationItem(item, index, options, result);
|
|
775
|
+
if (typeof rendered === "string") target.insertAdjacentHTML("beforeend", rendered);
|
|
776
|
+
else if (rendered) target.append(rendered);
|
|
777
|
+
});
|
|
778
|
+
}
|
|
779
|
+
function getPaginationPages(currentPage, pageCount, maxPages) {
|
|
780
|
+
const pages = [];
|
|
781
|
+
if (pageCount <= maxPages) {
|
|
782
|
+
for (let page = 1; page <= pageCount; page += 1) pages.push(page);
|
|
783
|
+
return pages;
|
|
784
|
+
}
|
|
785
|
+
pages.push(1);
|
|
786
|
+
const sideCount = Math.max(1, Math.floor((maxPages - 3) / 2));
|
|
787
|
+
const start = Math.max(2, currentPage - sideCount);
|
|
788
|
+
const end = Math.min(pageCount - 1, currentPage + sideCount);
|
|
789
|
+
/* v8 ignore next -- pagination window shape depends on current page */
|
|
790
|
+
if (start > 2) pages.push("ellipsis-start");
|
|
791
|
+
for (let page = start; page <= end; page += 1) pages.push(page);
|
|
792
|
+
if (end < pageCount - 1) pages.push("ellipsis-end");
|
|
793
|
+
pages.push(pageCount);
|
|
794
|
+
return pages;
|
|
795
|
+
}
|
|
796
|
+
function createPaginationButton(label, action, disabled) {
|
|
797
|
+
const button = document.createElement("button");
|
|
798
|
+
button.type = "button";
|
|
799
|
+
button.setAttribute("data-pagination-action", action);
|
|
800
|
+
button.textContent = label;
|
|
801
|
+
button.disabled = disabled;
|
|
802
|
+
return button;
|
|
803
|
+
}
|
|
804
|
+
function renderPaginationControls(rootElement, result) {
|
|
805
|
+
/* v8 ignore next -- pagination controls render after setup stores options */
|
|
806
|
+
const options = paginationOptions.get(rootElement) || {};
|
|
807
|
+
const pageCount = result.pageCount;
|
|
808
|
+
const page = Math.min(result.page, pageCount);
|
|
809
|
+
const maxPages = getPaginationNumber(options.maxPages, 7);
|
|
810
|
+
const summary = document.createElement("span");
|
|
811
|
+
const pages = document.createElement("span");
|
|
812
|
+
const size = document.createElement("span");
|
|
813
|
+
const label = document.createElement("label");
|
|
814
|
+
const select = document.createElement("select");
|
|
815
|
+
summary.className = "pagination-summary";
|
|
816
|
+
summary.textContent = "Page " + page + " of " + pageCount + " (" + result.total + " items)";
|
|
817
|
+
pages.className = "pagination-pages";
|
|
818
|
+
pages.setAttribute("role", "group");
|
|
819
|
+
pages.setAttribute("aria-label", "Pages");
|
|
820
|
+
pages.append(createPaginationButton("Previous", "previous", page <= 1));
|
|
821
|
+
getPaginationPages(page, pageCount, maxPages).forEach((pageNumber) => {
|
|
822
|
+
if (typeof pageNumber !== "number") {
|
|
823
|
+
const ellipsis = document.createElement("span");
|
|
824
|
+
ellipsis.className = "pagination-ellipsis";
|
|
825
|
+
ellipsis.setAttribute("aria-hidden", "true");
|
|
826
|
+
ellipsis.textContent = "...";
|
|
827
|
+
pages.append(ellipsis);
|
|
828
|
+
return;
|
|
829
|
+
}
|
|
830
|
+
const button = createPaginationButton(String(pageNumber), "page", false);
|
|
831
|
+
button.className = "pagination-page";
|
|
832
|
+
button.setAttribute("data-pagination-page", String(pageNumber));
|
|
833
|
+
button.setAttribute("aria-label", "Page " + pageNumber);
|
|
834
|
+
if (pageNumber === page) button.setAttribute("aria-current", "page");
|
|
835
|
+
pages.append(button);
|
|
836
|
+
});
|
|
837
|
+
pages.append(createPaginationButton("Next", "next", page >= pageCount));
|
|
838
|
+
size.className = "pagination-size";
|
|
839
|
+
label.textContent = "Page size";
|
|
840
|
+
(options.pageSizes || [result.pageSize]).forEach((pageSize) => {
|
|
841
|
+
const option = document.createElement("option");
|
|
842
|
+
option.value = String(pageSize);
|
|
843
|
+
option.textContent = String(pageSize);
|
|
844
|
+
option.selected = pageSize === result.pageSize;
|
|
845
|
+
select.append(option);
|
|
846
|
+
});
|
|
847
|
+
select.setAttribute("data-pagination-size", "");
|
|
848
|
+
label.append(select);
|
|
849
|
+
size.append(label);
|
|
850
|
+
rootElement.replaceChildren(summary, pages, size);
|
|
851
|
+
}
|
|
852
|
+
function setPaginationLoading(rootElement, loading) {
|
|
853
|
+
rootElement.setAttribute("aria-busy", loading ? "true" : "false");
|
|
854
|
+
rootElement.querySelectorAll("button, select").forEach((control) => {
|
|
855
|
+
control.disabled = loading;
|
|
856
|
+
});
|
|
857
|
+
}
|
|
858
|
+
function handlePaginationError(rootElement, state, request, error) {
|
|
859
|
+
if (request !== state.request) return;
|
|
860
|
+
if (state.result) renderPaginationControls(rootElement, state.result);
|
|
861
|
+
else setPaginationLoading(rootElement, false);
|
|
862
|
+
rootElement.dispatchEvent(new CustomEvent("pagination:error", {
|
|
863
|
+
bubbles: true,
|
|
864
|
+
detail: { error }
|
|
865
|
+
}));
|
|
866
|
+
}
|
|
867
|
+
function updatePagination(rootElement) {
|
|
868
|
+
const options = rootElement ? paginationOptions.get(rootElement) : null;
|
|
869
|
+
const state = rootElement ? paginationState.get(rootElement) : null;
|
|
870
|
+
if (!rootElement || !options || !state) return rootElement;
|
|
871
|
+
state.request += 1;
|
|
872
|
+
const request = state.request;
|
|
873
|
+
setPaginationLoading(rootElement, true);
|
|
874
|
+
try {
|
|
875
|
+
Promise.resolve(createPaginationResult(rootElement, options, state)).then((rawResult) => {
|
|
876
|
+
if (request !== state.request) return;
|
|
877
|
+
const result = normalizePaginationResult(rawResult, state);
|
|
878
|
+
state.page = Math.min(result.page, result.pageCount);
|
|
879
|
+
state.pageSize = result.pageSize;
|
|
880
|
+
state.result = result;
|
|
881
|
+
renderPaginationItems(rootElement, result);
|
|
882
|
+
renderPaginationControls(rootElement, result);
|
|
883
|
+
rootElement.dispatchEvent(new CustomEvent("pagination:change", {
|
|
884
|
+
bubbles: true,
|
|
885
|
+
detail: result
|
|
886
|
+
}));
|
|
887
|
+
}).catch((error) => {
|
|
888
|
+
handlePaginationError(rootElement, state, request, error);
|
|
889
|
+
});
|
|
890
|
+
} catch (error) {
|
|
891
|
+
handlePaginationError(rootElement, state, request, error);
|
|
892
|
+
}
|
|
893
|
+
return rootElement;
|
|
894
|
+
}
|
|
895
|
+
function setPaginationPage(rootElement, page) {
|
|
896
|
+
const state = rootElement ? paginationState.get(rootElement) : null;
|
|
897
|
+
if (!state) return null;
|
|
898
|
+
state.page = getPaginationNumber(page, state.page);
|
|
899
|
+
return updatePagination(rootElement);
|
|
900
|
+
}
|
|
901
|
+
function setPaginationPageSize(rootElement, pageSize) {
|
|
902
|
+
const state = rootElement ? paginationState.get(rootElement) : null;
|
|
903
|
+
if (!state) return null;
|
|
904
|
+
state.page = 1;
|
|
905
|
+
state.pageSize = getPaginationNumber(pageSize, state.pageSize);
|
|
906
|
+
return updatePagination(rootElement);
|
|
907
|
+
}
|
|
908
|
+
function handlePaginationClick(event) {
|
|
909
|
+
const rootElement = resolvePagination(currentTargetElement(event));
|
|
910
|
+
const target = eventTargetElement(event);
|
|
911
|
+
/* v8 ignore next -- browser click events provide element targets */
|
|
912
|
+
const button = target ? target.closest("[data-pagination-action]") : null;
|
|
913
|
+
/* v8 ignore next -- delegated pagination events are wired after state exists */
|
|
914
|
+
const state = rootElement ? paginationState.get(rootElement) : null;
|
|
915
|
+
if (!button || !state || button.disabled) return;
|
|
916
|
+
const action = button.getAttribute("data-pagination-action");
|
|
917
|
+
/* v8 ignore next 7 -- pagination action dispatch ignores unknown delegated actions */
|
|
918
|
+
if (action === "previous") setPaginationPage(rootElement, state.page - 1);
|
|
919
|
+
else if (action === "next") setPaginationPage(rootElement, state.page + 1);
|
|
920
|
+
else if (action === "page") setPaginationPage(rootElement, button.getAttribute("data-pagination-page"));
|
|
921
|
+
}
|
|
922
|
+
function handlePaginationChange(event) {
|
|
923
|
+
const target = eventTargetElement(event);
|
|
924
|
+
const rootElement = resolvePagination(currentTargetElement(event));
|
|
925
|
+
/* v8 ignore next -- delegated change handler ignores non-page-size controls */
|
|
926
|
+
if (isSelectElement(target) && rootElement && target.matches("[data-pagination-size]")) setPaginationPageSize(rootElement, target.value);
|
|
927
|
+
}
|
|
928
|
+
function setupPagination(target, options = {}) {
|
|
929
|
+
const rootElement = resolvePagination(target);
|
|
930
|
+
if (!rootElement) return null;
|
|
931
|
+
/* v8 ignore next -- setup accepts fresh options, merged options, and nullish fallbacks */
|
|
932
|
+
const nextOptions = Object.assign({}, paginationOptions.get(rootElement), options || {});
|
|
933
|
+
nextOptions.target = getPaginationTarget(rootElement, nextOptions);
|
|
934
|
+
nextOptions.pageSizes = getPaginationPageSizes(rootElement, nextOptions);
|
|
935
|
+
nextOptions.pageSize = getPaginationNumber(nextOptions.pageSize || rootElement.getAttribute("data-page-size"), nextOptions.pageSizes[0]);
|
|
936
|
+
nextOptions.maxPages = getPaginationNumber(nextOptions.maxPages || rootElement.getAttribute("data-max-pages"), 7);
|
|
937
|
+
const normalizedOptions = {
|
|
938
|
+
data: nextOptions.data,
|
|
939
|
+
load: nextOptions.load,
|
|
940
|
+
maxPages: nextOptions.maxPages,
|
|
941
|
+
pageSize: nextOptions.pageSize,
|
|
942
|
+
pageSizes: nextOptions.pageSizes,
|
|
943
|
+
renderItem: nextOptions.renderItem,
|
|
944
|
+
target: isElement(nextOptions.target) ? nextOptions.target : null
|
|
945
|
+
};
|
|
946
|
+
paginationOptions.set(rootElement, normalizedOptions);
|
|
947
|
+
if (paginationState.has(rootElement)) {
|
|
948
|
+
const state = paginationState.get(rootElement);
|
|
949
|
+
/* v8 ignore next -- repeated setup may update page size or preserve existing state */
|
|
950
|
+
if (state && options.pageSize) {
|
|
951
|
+
state.page = 1;
|
|
952
|
+
state.pageSize = nextOptions.pageSize;
|
|
953
|
+
}
|
|
954
|
+
} else paginationState.set(rootElement, {
|
|
955
|
+
page: getPaginationNumber(rootElement.getAttribute("data-page"), 1),
|
|
956
|
+
pageSize: nextOptions.pageSize,
|
|
957
|
+
request: 0
|
|
958
|
+
});
|
|
959
|
+
if (!wiredPaginations.has(rootElement)) {
|
|
960
|
+
wiredPaginations.add(rootElement);
|
|
961
|
+
listen(rootElement, "click", handlePaginationClick);
|
|
962
|
+
listen(rootElement, "change", handlePaginationChange);
|
|
963
|
+
}
|
|
964
|
+
updatePagination(rootElement);
|
|
965
|
+
return rootElement;
|
|
966
|
+
}
|
|
967
|
+
legacy.pagination = {
|
|
968
|
+
setup(target, options) {
|
|
969
|
+
return setupPagination(target, options);
|
|
970
|
+
},
|
|
971
|
+
goTo(target, page) {
|
|
972
|
+
return setPaginationPage(resolvePagination(target), page);
|
|
973
|
+
},
|
|
974
|
+
pageSize(target, pageSize) {
|
|
975
|
+
return setPaginationPageSize(resolvePagination(target), pageSize);
|
|
976
|
+
},
|
|
977
|
+
refresh(target) {
|
|
978
|
+
return updatePagination(resolvePagination(target));
|
|
979
|
+
}
|
|
980
|
+
};
|
|
981
|
+
document.addEventListener("DOMContentLoaded", function() {
|
|
982
|
+
document.querySelectorAll("[data-pagination]").forEach((rootElement) => {
|
|
983
|
+
setupPagination(rootElement);
|
|
984
|
+
});
|
|
985
|
+
});
|
|
986
|
+
}
|
|
987
|
+
//#endregion
|
|
988
|
+
//#region src/features/popover.ts
|
|
989
|
+
var legacyPopoverPlacements = [
|
|
990
|
+
"top",
|
|
991
|
+
"right",
|
|
992
|
+
"bottom",
|
|
993
|
+
"left"
|
|
994
|
+
];
|
|
995
|
+
var wiredPopoverTriggers = /* @__PURE__ */ new WeakSet();
|
|
996
|
+
var popoverTriggerSelector = "[data-popover-target], [data-popover]";
|
|
997
|
+
var openPopoverTrigger = null;
|
|
998
|
+
function isPopoverPlacement(placement) {
|
|
999
|
+
return typeof placement === "string" && legacyPopoverPlacements.includes(placement);
|
|
1000
|
+
}
|
|
1001
|
+
function installPopover(legacy) {
|
|
1002
|
+
function resolveElement(target) {
|
|
1003
|
+
if (!target) return null;
|
|
1004
|
+
if (isLegacyCollection(target)) return resolveElement(target[0]);
|
|
1005
|
+
if (typeof target === "string") return document.querySelector(target);
|
|
1006
|
+
/* v8 ignore next -- resolver fallback for non-element internal callers */
|
|
1007
|
+
return isElement(target) ? target : null;
|
|
1008
|
+
}
|
|
1009
|
+
function resolvePopoverTrigger(target) {
|
|
1010
|
+
const element = resolveElement(target);
|
|
1011
|
+
if (!element) return null;
|
|
1012
|
+
if (element.matches(popoverTriggerSelector)) return element;
|
|
1013
|
+
return element.closest(popoverTriggerSelector);
|
|
1014
|
+
}
|
|
1015
|
+
function resolvePopover(target) {
|
|
1016
|
+
const element = resolveElement(target);
|
|
1017
|
+
if (!element) return null;
|
|
1018
|
+
if (element.matches(".popover, [data-popover-content]")) return element;
|
|
1019
|
+
const trigger = resolvePopoverTrigger(element);
|
|
1020
|
+
/* v8 ignore next -- public close/open paths resolve trigger and content separately */
|
|
1021
|
+
return trigger ? getTriggerPopover(trigger) : null;
|
|
1022
|
+
}
|
|
1023
|
+
function getTriggerPopover(trigger) {
|
|
1024
|
+
const target = trigger.getAttribute("data-popover-target") || trigger.getAttribute("data-popover") || trigger.getAttribute("aria-controls");
|
|
1025
|
+
if (!target) return null;
|
|
1026
|
+
if (target.charAt(0) === "#") return document.querySelector(target);
|
|
1027
|
+
return document.getElementById(target);
|
|
1028
|
+
}
|
|
1029
|
+
function getPopoverPlacement(trigger) {
|
|
1030
|
+
const placement = trigger.getAttribute("data-popover-placement");
|
|
1031
|
+
return isPopoverPlacement(placement) ? placement : "bottom";
|
|
1032
|
+
}
|
|
1033
|
+
function positionPopover(trigger, popover) {
|
|
1034
|
+
const gap = 4;
|
|
1035
|
+
const margin = 8;
|
|
1036
|
+
const placement = getPopoverPlacement(trigger);
|
|
1037
|
+
const triggerRect = trigger.getBoundingClientRect();
|
|
1038
|
+
const popoverRect = popover.getBoundingClientRect();
|
|
1039
|
+
let top = triggerRect.bottom + gap;
|
|
1040
|
+
let left = triggerRect.left;
|
|
1041
|
+
if (placement === "top") {
|
|
1042
|
+
top = triggerRect.top - popoverRect.height - gap;
|
|
1043
|
+
left = triggerRect.left;
|
|
1044
|
+
} else if (placement === "right") {
|
|
1045
|
+
top = triggerRect.top;
|
|
1046
|
+
left = triggerRect.right + gap;
|
|
1047
|
+
} else if (placement === "left") {
|
|
1048
|
+
top = triggerRect.top;
|
|
1049
|
+
left = triggerRect.left - popoverRect.width - gap;
|
|
1050
|
+
}
|
|
1051
|
+
top = Math.max(margin, Math.min(top, window.innerHeight - popoverRect.height - margin));
|
|
1052
|
+
left = Math.max(margin, Math.min(left, window.innerWidth - popoverRect.width - margin));
|
|
1053
|
+
popover.style.top = top + "px";
|
|
1054
|
+
popover.style.left = left + "px";
|
|
1055
|
+
}
|
|
1056
|
+
function closePopover(trigger) {
|
|
1057
|
+
/* v8 ignore next -- trigger fallback is driven by document-level close handlers */
|
|
1058
|
+
const currentTrigger = trigger || openPopoverTrigger;
|
|
1059
|
+
/* v8 ignore next -- paired with currentTrigger fallback above */
|
|
1060
|
+
const popover = currentTrigger ? getTriggerPopover(currentTrigger) : null;
|
|
1061
|
+
if (!currentTrigger || !popover) return null;
|
|
1062
|
+
popover.hidden = true;
|
|
1063
|
+
currentTrigger.setAttribute("aria-expanded", "false");
|
|
1064
|
+
/* v8 ignore next -- depends on whether close was invoked directly or globally */
|
|
1065
|
+
if (openPopoverTrigger === currentTrigger) openPopoverTrigger = null;
|
|
1066
|
+
return popover;
|
|
1067
|
+
}
|
|
1068
|
+
function openPopover(trigger) {
|
|
1069
|
+
/* v8 ignore next -- null trigger is covered by the public no-op API branch */
|
|
1070
|
+
const popover = trigger ? getTriggerPopover(trigger) : null;
|
|
1071
|
+
if (!trigger || !popover) return null;
|
|
1072
|
+
if (openPopoverTrigger && openPopoverTrigger !== trigger) closePopover(openPopoverTrigger);
|
|
1073
|
+
wirePopoverTrigger(trigger);
|
|
1074
|
+
popover.hidden = false;
|
|
1075
|
+
trigger.setAttribute("aria-expanded", "true");
|
|
1076
|
+
openPopoverTrigger = trigger;
|
|
1077
|
+
positionPopover(trigger, popover);
|
|
1078
|
+
return popover;
|
|
1079
|
+
}
|
|
1080
|
+
function togglePopover(trigger) {
|
|
1081
|
+
/* v8 ignore next -- null trigger is covered by the public no-op API branch */
|
|
1082
|
+
const popover = trigger ? getTriggerPopover(trigger) : null;
|
|
1083
|
+
if (!popover) return null;
|
|
1084
|
+
return popover.hidden ? openPopover(trigger) : closePopover(trigger);
|
|
1085
|
+
}
|
|
1086
|
+
function handlePopoverClick(event) {
|
|
1087
|
+
event.preventDefault();
|
|
1088
|
+
togglePopover(currentTargetElement(event));
|
|
1089
|
+
}
|
|
1090
|
+
function handlePopoverKeydown(event) {
|
|
1091
|
+
if (event.key !== "Escape") return;
|
|
1092
|
+
const trigger = currentTargetElement(event);
|
|
1093
|
+
/* v8 ignore next -- browser-dispatched listener events always provide the trigger as currentTarget */
|
|
1094
|
+
if (!trigger) return;
|
|
1095
|
+
const popover = getTriggerPopover(trigger);
|
|
1096
|
+
if (!popover || popover.hidden) return;
|
|
1097
|
+
event.preventDefault();
|
|
1098
|
+
closePopover(trigger);
|
|
1099
|
+
/* v8 ignore next -- focusability is browser/element dependent */
|
|
1100
|
+
if (trigger instanceof HTMLElement) trigger.focus();
|
|
1101
|
+
}
|
|
1102
|
+
function handleDocumentPopoverClick(event) {
|
|
1103
|
+
if (!openPopoverTrigger) return;
|
|
1104
|
+
const target = eventTargetElement(event);
|
|
1105
|
+
if (!target) return;
|
|
1106
|
+
const popover = getTriggerPopover(openPopoverTrigger);
|
|
1107
|
+
if (openPopoverTrigger.contains(target) || popover && popover.contains(target)) return;
|
|
1108
|
+
closePopover(openPopoverTrigger);
|
|
1109
|
+
}
|
|
1110
|
+
function handleDocumentPopoverKeydown(event) {
|
|
1111
|
+
if (event.key !== "Escape" || !openPopoverTrigger) return;
|
|
1112
|
+
event.preventDefault();
|
|
1113
|
+
closePopover(openPopoverTrigger);
|
|
1114
|
+
}
|
|
1115
|
+
function updateOpenPopoverPosition() {
|
|
1116
|
+
if (!openPopoverTrigger) return;
|
|
1117
|
+
const popover = getTriggerPopover(openPopoverTrigger);
|
|
1118
|
+
/* v8 ignore next -- resize/scroll repositioning depends on current open state */
|
|
1119
|
+
if (popover && !popover.hidden) positionPopover(openPopoverTrigger, popover);
|
|
1120
|
+
}
|
|
1121
|
+
function wirePopoverTrigger(trigger) {
|
|
1122
|
+
const popover = getTriggerPopover(trigger);
|
|
1123
|
+
if (!popover) return trigger;
|
|
1124
|
+
/* v8 ignore next -- target id resolution is already validated before this repair branch */
|
|
1125
|
+
if (!popover.id && trigger.getAttribute("data-popover-target"))
|
|
1126
|
+
/* v8 ignore next -- a target resolved by selector already has the id used to resolve it */
|
|
1127
|
+
popover.id = (trigger.getAttribute("data-popover-target") || "").replace(/^#/, "");
|
|
1128
|
+
trigger.setAttribute("aria-haspopup", "dialog");
|
|
1129
|
+
/* v8 ignore next -- initial aria-expanded mirrors caller-provided hidden state */
|
|
1130
|
+
trigger.setAttribute("aria-expanded", popover.hidden ? "false" : "true");
|
|
1131
|
+
if (popover.id)
|
|
1132
|
+
/* v8 ignore next -- aria-controls is only omitted for anonymous popover content */
|
|
1133
|
+
trigger.setAttribute("aria-controls", popover.id);
|
|
1134
|
+
if (!popover.hasAttribute("role")) popover.setAttribute("role", "dialog");
|
|
1135
|
+
if (!wiredPopoverTriggers.has(trigger)) {
|
|
1136
|
+
wiredPopoverTriggers.add(trigger);
|
|
1137
|
+
listen(trigger, "click", handlePopoverClick);
|
|
1138
|
+
listen(trigger, "keydown", handlePopoverKeydown);
|
|
1139
|
+
}
|
|
1140
|
+
return trigger;
|
|
1141
|
+
}
|
|
1142
|
+
function setupPopover(target) {
|
|
1143
|
+
const trigger = resolvePopoverTrigger(target);
|
|
1144
|
+
return trigger ? wirePopoverTrigger(trigger) : null;
|
|
1145
|
+
}
|
|
1146
|
+
legacy.popover = {
|
|
1147
|
+
setup(target) {
|
|
1148
|
+
return setupPopover(target);
|
|
1149
|
+
},
|
|
1150
|
+
open(target) {
|
|
1151
|
+
return openPopover(resolvePopoverTrigger(target));
|
|
1152
|
+
},
|
|
1153
|
+
close(target) {
|
|
1154
|
+
const trigger = resolvePopoverTrigger(target);
|
|
1155
|
+
if (trigger) return closePopover(trigger);
|
|
1156
|
+
const popover = resolvePopover(target);
|
|
1157
|
+
return popover && openPopoverTrigger && getTriggerPopover(openPopoverTrigger) === popover ? closePopover(openPopoverTrigger) : popover;
|
|
1158
|
+
},
|
|
1159
|
+
toggle(target) {
|
|
1160
|
+
return togglePopover(resolvePopoverTrigger(target));
|
|
1161
|
+
}
|
|
1162
|
+
};
|
|
1163
|
+
document.addEventListener("DOMContentLoaded", function() {
|
|
1164
|
+
document.querySelectorAll(popoverTriggerSelector).forEach(setupPopover);
|
|
1165
|
+
});
|
|
1166
|
+
document.addEventListener("click", handleDocumentPopoverClick);
|
|
1167
|
+
document.addEventListener("keydown", handleDocumentPopoverKeydown);
|
|
1168
|
+
window.addEventListener("resize", updateOpenPopoverPosition);
|
|
1169
|
+
window.addEventListener("scroll", updateOpenPopoverPosition, true);
|
|
1170
|
+
}
|
|
1171
|
+
//#endregion
|
|
1172
|
+
//#region src/features/tabs.ts
|
|
1173
|
+
var wiredTabs = /* @__PURE__ */ new WeakSet();
|
|
1174
|
+
function installTabs(legacy) {
|
|
1175
|
+
function resolveTabs(target) {
|
|
1176
|
+
if (!target) return null;
|
|
1177
|
+
if (isLegacyCollection(target)) return resolveTabs(target[0]);
|
|
1178
|
+
if (typeof target === "string") return document.querySelector(target);
|
|
1179
|
+
if (isElement(target)) {
|
|
1180
|
+
if (target.matches("[data-tabs], .tabs")) return target;
|
|
1181
|
+
return target.closest("[data-tabs], .tabs");
|
|
1182
|
+
}
|
|
1183
|
+
return null;
|
|
1184
|
+
}
|
|
1185
|
+
function getTabs(rootElement) {
|
|
1186
|
+
const tabList = Array.from(rootElement.children).find((element) => element.matches("[role=\"tablist\"], .tabs-list"));
|
|
1187
|
+
if (!tabList) return [];
|
|
1188
|
+
return Array.from(tabList.children).filter((element) => element.matches("[role=\"tab\"]"));
|
|
1189
|
+
}
|
|
1190
|
+
function getTabPanels(rootElement) {
|
|
1191
|
+
return Array.from(rootElement.children).filter((element) => element.matches("[role=\"tabpanel\"]"));
|
|
1192
|
+
}
|
|
1193
|
+
function getTabPanel(rootElement, tab) {
|
|
1194
|
+
const panelId = tab.getAttribute("aria-controls");
|
|
1195
|
+
if (!panelId) return null;
|
|
1196
|
+
try {
|
|
1197
|
+
return rootElement.querySelector("#" + CSS.escape(panelId));
|
|
1198
|
+
} catch (error) {
|
|
1199
|
+
return document.getElementById(panelId);
|
|
1200
|
+
}
|
|
1201
|
+
}
|
|
1202
|
+
function selectTab(tab, setFocus) {
|
|
1203
|
+
const rootElement = resolveTabs(tab);
|
|
1204
|
+
if (!rootElement || !tab) return null;
|
|
1205
|
+
getTabs(rootElement).forEach((currentTab) => {
|
|
1206
|
+
const selected = currentTab === tab;
|
|
1207
|
+
const panel = getTabPanel(rootElement, currentTab);
|
|
1208
|
+
currentTab.setAttribute("aria-selected", selected ? "true" : "false");
|
|
1209
|
+
currentTab.setAttribute("tabindex", selected ? "0" : "-1");
|
|
1210
|
+
if (panel) panel.hidden = !selected;
|
|
1211
|
+
});
|
|
1212
|
+
if (setFocus && tab instanceof HTMLElement) tab.focus();
|
|
1213
|
+
return tab;
|
|
1214
|
+
}
|
|
1215
|
+
function selectTabByIndex(rootElement, index, setFocus) {
|
|
1216
|
+
const tab = getTabs(rootElement)[index];
|
|
1217
|
+
if (!tab) return null;
|
|
1218
|
+
return selectTab(tab, setFocus);
|
|
1219
|
+
}
|
|
1220
|
+
function handleTabClick(event) {
|
|
1221
|
+
const target = eventTargetElement(event);
|
|
1222
|
+
/* v8 ignore next -- browser click events provide element targets for delegated tab clicks */
|
|
1223
|
+
const tab = target ? target.closest("[role=\"tab\"]") : null;
|
|
1224
|
+
/* v8 ignore next -- delegated no-op branch for non-tab clicks */
|
|
1225
|
+
if (tab) selectTab(tab, false);
|
|
1226
|
+
}
|
|
1227
|
+
function handleTabKeydown(event) {
|
|
1228
|
+
const rootElement = resolveTabs(currentTargetElement(event));
|
|
1229
|
+
const target = eventTargetElement(event);
|
|
1230
|
+
if (!rootElement || !target) return;
|
|
1231
|
+
const tabs = getTabs(rootElement);
|
|
1232
|
+
const currentIndex = tabs.indexOf(target);
|
|
1233
|
+
/* v8 ignore next -- tab keyboard listeners are scoped to tab controls in normal use */
|
|
1234
|
+
if (currentIndex < 0) return;
|
|
1235
|
+
let nextIndex = currentIndex;
|
|
1236
|
+
if (event.key === "ArrowRight" || event.key === "ArrowDown") nextIndex = (currentIndex + 1) % tabs.length;
|
|
1237
|
+
else if (event.key === "ArrowLeft" || event.key === "ArrowUp") nextIndex = (currentIndex - 1 + tabs.length) % tabs.length;
|
|
1238
|
+
else if (event.key === "Home") nextIndex = 0;
|
|
1239
|
+
else if (event.key === "End") nextIndex = tabs.length - 1;
|
|
1240
|
+
else return;
|
|
1241
|
+
event.preventDefault();
|
|
1242
|
+
selectTabByIndex(rootElement, nextIndex, true);
|
|
1243
|
+
}
|
|
1244
|
+
function setupTabs(target) {
|
|
1245
|
+
const rootElement = resolveTabs(target);
|
|
1246
|
+
if (!rootElement || wiredTabs.has(rootElement)) return rootElement;
|
|
1247
|
+
wiredTabs.add(rootElement);
|
|
1248
|
+
listen(rootElement, "click", handleTabClick);
|
|
1249
|
+
listen(rootElement, "keydown", handleTabKeydown);
|
|
1250
|
+
const tabs = getTabs(rootElement);
|
|
1251
|
+
const selectedTab = tabs.find((tab) => tab.getAttribute("aria-selected") === "true") || tabs[0];
|
|
1252
|
+
getTabPanels(rootElement).forEach((panel) => {
|
|
1253
|
+
if (!panel.hasAttribute("tabindex")) panel.setAttribute("tabindex", "0");
|
|
1254
|
+
});
|
|
1255
|
+
if (selectedTab) selectTab(selectedTab, false);
|
|
1256
|
+
return rootElement;
|
|
1257
|
+
}
|
|
1258
|
+
legacy.tabs = {
|
|
1259
|
+
setup(target) {
|
|
1260
|
+
return setupTabs(target);
|
|
1261
|
+
},
|
|
1262
|
+
select(target, index) {
|
|
1263
|
+
const rootElement = resolveTabs(target);
|
|
1264
|
+
if (!rootElement) return null;
|
|
1265
|
+
if (typeof index === "number") return selectTabByIndex(rootElement, index, false);
|
|
1266
|
+
return selectTab(rootElement.querySelector(index), false);
|
|
1267
|
+
}
|
|
1268
|
+
};
|
|
1269
|
+
document.addEventListener("DOMContentLoaded", function() {
|
|
1270
|
+
document.querySelectorAll("[data-tabs], .tabs").forEach(setupTabs);
|
|
1271
|
+
});
|
|
1272
|
+
}
|
|
1273
|
+
//#endregion
|
|
1274
|
+
//#region src/features/theme.ts
|
|
1275
|
+
var legacyThemes = ["light", "dark"];
|
|
1276
|
+
var legacyThemeStorageKey = "legacy.css.theme";
|
|
1277
|
+
function installTheme(legacy) {
|
|
1278
|
+
function isLegacyTheme(theme) {
|
|
1279
|
+
return typeof theme === "string" && legacyThemes.includes(theme);
|
|
1280
|
+
}
|
|
1281
|
+
function normalizeTheme(theme) {
|
|
1282
|
+
return isLegacyTheme(theme) ? theme : "light";
|
|
1283
|
+
}
|
|
1284
|
+
function getStoredTheme() {
|
|
1285
|
+
try {
|
|
1286
|
+
return window.localStorage.getItem(legacyThemeStorageKey);
|
|
1287
|
+
} catch (error) {
|
|
1288
|
+
return null;
|
|
1289
|
+
}
|
|
1290
|
+
}
|
|
1291
|
+
function storeTheme(theme) {
|
|
1292
|
+
try {
|
|
1293
|
+
window.localStorage.setItem(legacyThemeStorageKey, theme);
|
|
1294
|
+
} catch (error) {
|
|
1295
|
+
return;
|
|
1296
|
+
}
|
|
1297
|
+
}
|
|
1298
|
+
function applyTheme(theme, persist = true) {
|
|
1299
|
+
const nextTheme = normalizeTheme(theme);
|
|
1300
|
+
document.documentElement.dataset.legacyTheme = nextTheme;
|
|
1301
|
+
if (persist) storeTheme(nextTheme);
|
|
1302
|
+
return nextTheme;
|
|
1303
|
+
}
|
|
1304
|
+
legacy.theme = {
|
|
1305
|
+
apply(theme) {
|
|
1306
|
+
return applyTheme(theme || getStoredTheme());
|
|
1307
|
+
},
|
|
1308
|
+
get() {
|
|
1309
|
+
return normalizeTheme(document.documentElement.dataset.legacyTheme || getStoredTheme());
|
|
1310
|
+
},
|
|
1311
|
+
set(theme) {
|
|
1312
|
+
return applyTheme(theme);
|
|
1313
|
+
}
|
|
1314
|
+
};
|
|
1315
|
+
if (isLegacyTheme(getStoredTheme())) applyTheme(getStoredTheme(), false);
|
|
1316
|
+
}
|
|
1317
|
+
//#endregion
|
|
1318
|
+
//#region src/features/toast.ts
|
|
1319
|
+
var legacyToastPositions = [
|
|
1320
|
+
"top-left",
|
|
1321
|
+
"top-right",
|
|
1322
|
+
"bottom-left",
|
|
1323
|
+
"bottom-right"
|
|
1324
|
+
];
|
|
1325
|
+
var toastTimers = /* @__PURE__ */ new WeakMap();
|
|
1326
|
+
function isToastPosition(position) {
|
|
1327
|
+
return typeof position === "string" && legacyToastPositions.includes(position);
|
|
1328
|
+
}
|
|
1329
|
+
function installToast(legacy) {
|
|
1330
|
+
function resolveToast(target) {
|
|
1331
|
+
if (!target) return null;
|
|
1332
|
+
if (isLegacyCollection(target)) return resolveToast(target[0]);
|
|
1333
|
+
if (typeof target === "string") return document.querySelector(target);
|
|
1334
|
+
if (isElement(target) && target.matches(".toast, [data-toast]")) return target;
|
|
1335
|
+
return null;
|
|
1336
|
+
}
|
|
1337
|
+
function normalizeToastPosition(position) {
|
|
1338
|
+
return isToastPosition(position) ? position : "bottom-right";
|
|
1339
|
+
}
|
|
1340
|
+
function getToastRegion(position) {
|
|
1341
|
+
const normalizedPosition = normalizeToastPosition(position);
|
|
1342
|
+
let region = document.querySelector("[data-toast-region][data-position=\"" + normalizedPosition + "\"], .toast-region[data-position=\"" + normalizedPosition + "\"]");
|
|
1343
|
+
if (region) return region;
|
|
1344
|
+
region = document.createElement("div");
|
|
1345
|
+
region.className = "toast-region";
|
|
1346
|
+
region.dataset.position = normalizedPosition;
|
|
1347
|
+
region.dataset.toastRegion = "";
|
|
1348
|
+
region.setAttribute("aria-live", "polite");
|
|
1349
|
+
region.setAttribute("aria-atomic", "false");
|
|
1350
|
+
document.body.append(region);
|
|
1351
|
+
return region;
|
|
1352
|
+
}
|
|
1353
|
+
function normalizeToastOptions(message, options) {
|
|
1354
|
+
if (message && typeof message === "object" && !("nodeType" in message) && !("jquery" in message)) return Object.assign({}, message);
|
|
1355
|
+
return Object.assign({}, options, { message: isLegacyCollection(message) ? message[0] : message });
|
|
1356
|
+
}
|
|
1357
|
+
function resolveToastContainer(target) {
|
|
1358
|
+
/* v8 ignore next -- public toast callers only resolve truthy container targets */
|
|
1359
|
+
if (!target) return null;
|
|
1360
|
+
if (isLegacyCollection(target)) return resolveToastContainer(target[0]);
|
|
1361
|
+
if (typeof target === "string") return document.querySelector(target);
|
|
1362
|
+
if (isElement(target)) return target;
|
|
1363
|
+
return null;
|
|
1364
|
+
}
|
|
1365
|
+
function setToastContent(toast, options) {
|
|
1366
|
+
const body = document.createElement("div");
|
|
1367
|
+
body.className = "toast-body";
|
|
1368
|
+
if (options.title) {
|
|
1369
|
+
const title = document.createElement("strong");
|
|
1370
|
+
title.className = "toast-title";
|
|
1371
|
+
title.textContent = options.title;
|
|
1372
|
+
body.append(title);
|
|
1373
|
+
}
|
|
1374
|
+
if (options.message && typeof options.message === "object" && "nodeType" in options.message) body.append(options.message);
|
|
1375
|
+
else {
|
|
1376
|
+
const message = document.createElement("span");
|
|
1377
|
+
message.textContent = options.message || "";
|
|
1378
|
+
body.append(message);
|
|
1379
|
+
}
|
|
1380
|
+
toast.append(body);
|
|
1381
|
+
}
|
|
1382
|
+
function closeToastElement(toast) {
|
|
1383
|
+
if (!toast) return null;
|
|
1384
|
+
const timer = toastTimers.get(toast);
|
|
1385
|
+
if (timer) {
|
|
1386
|
+
window.clearTimeout(timer);
|
|
1387
|
+
toastTimers.delete(toast);
|
|
1388
|
+
}
|
|
1389
|
+
toast.dispatchEvent(new CustomEvent("toast:close", { bubbles: true }));
|
|
1390
|
+
toast.remove();
|
|
1391
|
+
return toast;
|
|
1392
|
+
}
|
|
1393
|
+
function showToast(message, options) {
|
|
1394
|
+
const nextOptions = normalizeToastOptions(message, options);
|
|
1395
|
+
const type = nextOptions.type && [
|
|
1396
|
+
"info",
|
|
1397
|
+
"success",
|
|
1398
|
+
"warning",
|
|
1399
|
+
"danger",
|
|
1400
|
+
"muted"
|
|
1401
|
+
].includes(nextOptions.type) ? nextOptions.type : "info";
|
|
1402
|
+
const region = nextOptions.container ? resolveToastContainer(nextOptions.container) : getToastRegion(nextOptions.position);
|
|
1403
|
+
const toast = document.createElement("section");
|
|
1404
|
+
const duration = typeof nextOptions.duration === "number" ? nextOptions.duration : 5e3;
|
|
1405
|
+
if (!region) return null;
|
|
1406
|
+
toast.className = "toast";
|
|
1407
|
+
toast.dataset.toast = "";
|
|
1408
|
+
toast.setAttribute("role", type === "danger" ? "alert" : "status");
|
|
1409
|
+
if (type !== "info") toast.classList.add("toast-" + type);
|
|
1410
|
+
setToastContent(toast, nextOptions);
|
|
1411
|
+
if (nextOptions.dismissible !== false) {
|
|
1412
|
+
const closeButton = document.createElement("button");
|
|
1413
|
+
closeButton.type = "button";
|
|
1414
|
+
closeButton.className = "toast-close";
|
|
1415
|
+
closeButton.setAttribute("aria-label", nextOptions.closeLabel || "Close notification");
|
|
1416
|
+
closeButton.textContent = nextOptions.closeText || "Close";
|
|
1417
|
+
closeButton.addEventListener("click", function() {
|
|
1418
|
+
closeToastElement(toast);
|
|
1419
|
+
});
|
|
1420
|
+
toast.append(closeButton);
|
|
1421
|
+
}
|
|
1422
|
+
region.append(toast);
|
|
1423
|
+
toast.dispatchEvent(new CustomEvent("toast:show", { bubbles: true }));
|
|
1424
|
+
if (duration > 0) toastTimers.set(toast, window.setTimeout(function() {
|
|
1425
|
+
closeToastElement(toast);
|
|
1426
|
+
}, duration));
|
|
1427
|
+
return toast;
|
|
1428
|
+
}
|
|
1429
|
+
function clearToasts(target) {
|
|
1430
|
+
/* v8 ignore next -- public clear covers both document and resolved-container behavior */
|
|
1431
|
+
const rootElement = target ? resolveToastContainer(target) : document;
|
|
1432
|
+
const toasts = rootElement ? Array.from(rootElement.querySelectorAll(".toast, [data-toast]")) : [];
|
|
1433
|
+
toasts.forEach(closeToastElement);
|
|
1434
|
+
return toasts;
|
|
1435
|
+
}
|
|
1436
|
+
legacy.toast = {
|
|
1437
|
+
show(message, options) {
|
|
1438
|
+
return showToast(message, options);
|
|
1439
|
+
},
|
|
1440
|
+
close(target) {
|
|
1441
|
+
return closeToastElement(resolveToast(target));
|
|
1442
|
+
},
|
|
1443
|
+
clear(target) {
|
|
1444
|
+
return clearToasts(target);
|
|
1445
|
+
}
|
|
1446
|
+
};
|
|
1447
|
+
}
|
|
1448
|
+
//#endregion
|
|
1449
|
+
//#region src/legacy.ts
|
|
1450
|
+
(function() {
|
|
1451
|
+
const root = window;
|
|
1452
|
+
/* v8 ignore next -- alternate startup path is preserving an existing namespace */
|
|
1453
|
+
if (!root.LegacyCss) root.LegacyCss = {};
|
|
1454
|
+
const legacy = root.LegacyCss;
|
|
1455
|
+
installTheme(legacy);
|
|
1456
|
+
installModal(legacy);
|
|
1457
|
+
installToast(legacy);
|
|
1458
|
+
installPopover(legacy);
|
|
1459
|
+
installTabs(legacy);
|
|
1460
|
+
installDragdrop(legacy);
|
|
1461
|
+
installMultiselect(legacy);
|
|
1462
|
+
installPagination(legacy);
|
|
1463
|
+
installJQueryBridges(root, legacy);
|
|
1464
|
+
})();
|
|
1465
|
+
//#endregion
|
|
1466
|
+
})();
|