@bkstg/design-systems 1.0.0 → 1.0.1
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/package.json +1 -1
- package/tether/latest/README.md +44 -0
- package/tether/latest/charts-core.mjs +349 -0
- package/tether/latest/charts.js +58 -0
- package/tether/latest/motion.js +168 -0
- package/tether/latest/react/ActivityFeed.jsx +18 -0
- package/tether/latest/react/Alert.jsx +28 -0
- package/tether/latest/react/Annotations.jsx +24 -0
- package/tether/latest/react/AppShell.jsx +87 -0
- package/tether/latest/react/Avatar.jsx +19 -0
- package/tether/latest/react/Badge.jsx +14 -0
- package/tether/latest/react/Banner.jsx +17 -0
- package/tether/latest/react/Breadcrumbs.jsx +23 -0
- package/tether/latest/react/Button.jsx +31 -0
- package/tether/latest/react/ButtonGroup.jsx +29 -0
- package/tether/latest/react/Calendar.jsx +57 -0
- package/tether/latest/react/CardHeader.jsx +14 -0
- package/tether/latest/react/Chart.jsx +42 -0
- package/tether/latest/react/Chat.jsx +34 -0
- package/tether/latest/react/Checkbox.jsx +32 -0
- package/tether/latest/react/CodeSnippet.jsx +17 -0
- package/tether/latest/react/ColorPicker.jsx +35 -0
- package/tether/latest/react/CommandMenu.jsx +54 -0
- package/tether/latest/react/DatePicker.jsx +34 -0
- package/tether/latest/react/DeviceFrame.jsx +12 -0
- package/tether/latest/react/Dialog.jsx +43 -0
- package/tether/latest/react/Divider.jsx +10 -0
- package/tether/latest/react/Editor.jsx +85 -0
- package/tether/latest/react/EmptyState.jsx +13 -0
- package/tether/latest/react/Field.jsx +32 -0
- package/tether/latest/react/FileUpload.jsx +38 -0
- package/tether/latest/react/Filters.jsx +19 -0
- package/tether/latest/react/Infographics.jsx +54 -0
- package/tether/latest/react/InlineCTA.jsx +14 -0
- package/tether/latest/react/LineChart.jsx +23 -0
- package/tether/latest/react/Marketing.jsx +161 -0
- package/tether/latest/react/Menu.jsx +76 -0
- package/tether/latest/react/Metric.jsx +28 -0
- package/tether/latest/react/PageHeader.jsx +22 -0
- package/tether/latest/react/Pagination.jsx +32 -0
- package/tether/latest/react/Progress.jsx +26 -0
- package/tether/latest/react/RadioGroup.jsx +27 -0
- package/tether/latest/react/Reveal.jsx +17 -0
- package/tether/latest/react/SectionHeader.jsx +24 -0
- package/tether/latest/react/Select.jsx +19 -0
- package/tether/latest/react/Sheet.jsx +25 -0
- package/tether/latest/react/SideNav.jsx +37 -0
- package/tether/latest/react/Skeleton.jsx +16 -0
- package/tether/latest/react/Slider.jsx +27 -0
- package/tether/latest/react/Spinner.jsx +14 -0
- package/tether/latest/react/Steps.jsx +22 -0
- package/tether/latest/react/Switch.jsx +11 -0
- package/tether/latest/react/Table.jsx +24 -0
- package/tether/latest/react/Tabs.jsx +80 -0
- package/tether/latest/react/Tag.jsx +34 -0
- package/tether/latest/react/Tooltip.jsx +25 -0
- package/tether/latest/react/Tree.jsx +41 -0
- package/tether/latest/react/VideoPlayer.jsx +57 -0
- package/tether/latest/react/cx.js +4 -0
- package/tether/latest/react/index.js +59 -0
- package/tether/latest/system.json +44 -0
- package/tether/latest/tether.css +1661 -0
- package/tether/latest/tether.js +703 -0
- package/tether/v1/README.md +44 -0
- package/tether/v1/charts-core.mjs +349 -0
- package/tether/v1/charts.js +58 -0
- package/tether/v1/motion.js +168 -0
- package/tether/v1/react/ActivityFeed.jsx +18 -0
- package/tether/v1/react/Alert.jsx +28 -0
- package/tether/v1/react/Annotations.jsx +24 -0
- package/tether/v1/react/AppShell.jsx +87 -0
- package/tether/v1/react/Avatar.jsx +19 -0
- package/tether/v1/react/Badge.jsx +14 -0
- package/tether/v1/react/Banner.jsx +17 -0
- package/tether/v1/react/Breadcrumbs.jsx +23 -0
- package/tether/v1/react/Button.jsx +31 -0
- package/tether/v1/react/ButtonGroup.jsx +29 -0
- package/tether/v1/react/Calendar.jsx +57 -0
- package/tether/v1/react/CardHeader.jsx +14 -0
- package/tether/v1/react/Chart.jsx +42 -0
- package/tether/v1/react/Chat.jsx +34 -0
- package/tether/v1/react/Checkbox.jsx +32 -0
- package/tether/v1/react/CodeSnippet.jsx +17 -0
- package/tether/v1/react/ColorPicker.jsx +35 -0
- package/tether/v1/react/CommandMenu.jsx +54 -0
- package/tether/v1/react/DatePicker.jsx +34 -0
- package/tether/v1/react/DeviceFrame.jsx +12 -0
- package/tether/v1/react/Dialog.jsx +43 -0
- package/tether/v1/react/Divider.jsx +10 -0
- package/tether/v1/react/Editor.jsx +85 -0
- package/tether/v1/react/EmptyState.jsx +13 -0
- package/tether/v1/react/Field.jsx +32 -0
- package/tether/v1/react/FileUpload.jsx +38 -0
- package/tether/v1/react/Filters.jsx +19 -0
- package/tether/v1/react/Infographics.jsx +54 -0
- package/tether/v1/react/InlineCTA.jsx +14 -0
- package/tether/v1/react/LineChart.jsx +23 -0
- package/tether/v1/react/Marketing.jsx +161 -0
- package/tether/v1/react/Menu.jsx +76 -0
- package/tether/v1/react/Metric.jsx +28 -0
- package/tether/v1/react/PageHeader.jsx +22 -0
- package/tether/v1/react/Pagination.jsx +32 -0
- package/tether/v1/react/Progress.jsx +26 -0
- package/tether/v1/react/RadioGroup.jsx +27 -0
- package/tether/v1/react/Reveal.jsx +17 -0
- package/tether/v1/react/SectionHeader.jsx +24 -0
- package/tether/v1/react/Select.jsx +19 -0
- package/tether/v1/react/Sheet.jsx +25 -0
- package/tether/v1/react/SideNav.jsx +37 -0
- package/tether/v1/react/Skeleton.jsx +16 -0
- package/tether/v1/react/Slider.jsx +27 -0
- package/tether/v1/react/Spinner.jsx +14 -0
- package/tether/v1/react/Steps.jsx +22 -0
- package/tether/v1/react/Switch.jsx +11 -0
- package/tether/v1/react/Table.jsx +24 -0
- package/tether/v1/react/Tabs.jsx +80 -0
- package/tether/v1/react/Tag.jsx +34 -0
- package/tether/v1/react/Tooltip.jsx +25 -0
- package/tether/v1/react/Tree.jsx +41 -0
- package/tether/v1/react/VideoPlayer.jsx +57 -0
- package/tether/v1/react/cx.js +4 -0
- package/tether/v1/react/index.js +59 -0
- package/tether/v1/system.json +44 -0
- package/tether/v1/tether.css +1661 -0
- package/tether/v1/tether.js +703 -0
|
@@ -0,0 +1,703 @@
|
|
|
1
|
+
// components.js: behavior for the kit's interactive components. Plain script, no framework, no build.
|
|
2
|
+
// Load after the markup (or with defer). It wires everything already on the page, and exposes
|
|
3
|
+
// window.UI so markup added later can be wired with UI.init(container).
|
|
4
|
+
//
|
|
5
|
+
// Tabs [data-ui-tabs] arrows, Home/End, roving focus, sliding indicator
|
|
6
|
+
// Dialog [data-ui-open="<dialog id>"] opens a <dialog class="ui-dialog"> as a modal;
|
|
7
|
+
// [data-ui-close] closes it; Escape and a click on the backdrop close it too;
|
|
8
|
+
// focus returns to whatever opened it
|
|
9
|
+
// Tooltip .ui-tooltip-wrap Escape hides an open tooltip until the pointer or focus leaves (WCAG 1.4.13)
|
|
10
|
+
// Chart .ui-chart[data-ui-chart] draws a line chart from data-values="3,5,4,…" (and data-labels="Jan,Feb,…");
|
|
11
|
+
// the values also become the chart's text alternative
|
|
12
|
+
// Tag [data-ui-dismiss] removes its .ui-tag; focus moves to the next remove button, or the
|
|
13
|
+
// previous one, or the tag list, never lost to the top of the page
|
|
14
|
+
// Checkbox [data-indeterminate] starts mixed; [data-ui-checkall="<name>"] checks every box of that name
|
|
15
|
+
// Slider .ui-slider the filled track follows the value; an <output for="<id>"> shows it
|
|
16
|
+
// Button group .ui-btngroup toggles aria-pressed; with data-ui-single, one at a time
|
|
17
|
+
// Menu [aria-haspopup="menu"] opens its aria-controls menu; arrows, Home/End, a letter jumps,
|
|
18
|
+
// Escape closes and focus returns; choosing fires "ui-select"
|
|
19
|
+
// Editor [data-ui-editor] toolbar tools run editing commands; arrow keys move between tools
|
|
20
|
+
// Video [data-ui-video] play/pause, seek, mute, full screen; k or Space plays and pauses
|
|
21
|
+
// Pages [data-ui-pagination] data-page and data-count draw the pages; choosing one fires "ui-page"
|
|
22
|
+
// Calendar [data-ui-calendar] a month grid: arrows, Page Up/Down, Home/End; choosing fires "ui-date"
|
|
23
|
+
// Date picker [data-ui-datepicker] a button that opens a calendar; Escape or a choice closes it
|
|
24
|
+
// Copy [data-ui-copy="#id"] copies that element's text
|
|
25
|
+
// Color [data-ui-colorpick] swatches, native picker and hex field kept in step; fires "ui-color"
|
|
26
|
+
// Command dialog[data-ui-command] ⌘K / Ctrl+K or [data-ui-command-open]; type, arrows, Enter fires "ui-command"
|
|
27
|
+
// Files [data-ui-dropzone] click or drop; lists the files (never uploads); fires "ui-files"
|
|
28
|
+
// Chips .ui-chip[aria-pressed] toggles; fires "ui-change"
|
|
29
|
+
// Chat [data-ui-chat] Enter sends, Shift+Enter a new line; fires "ui-send"
|
|
30
|
+
// Tree .ui-tree[role=tree] arrows, Home/End, Enter selects; fires "ui-select"
|
|
31
|
+
// Site header .ui-sitenav-toggle opens and closes its aria-controls panel; Escape closes it
|
|
32
|
+
// Banner [data-ui-banner-close] hides its banner; fires "ui-dismiss"
|
|
33
|
+
// Annotations .ui-annot [data-annot] a pin and its note light up together
|
|
34
|
+
(function () {
|
|
35
|
+
"use strict";
|
|
36
|
+
|
|
37
|
+
function tabs(root) {
|
|
38
|
+
if (root._uiTabs) return root._uiTabs;
|
|
39
|
+
var list = root.querySelector('[role="tablist"]');
|
|
40
|
+
var all = Array.prototype.slice.call(list.querySelectorAll('[role="tab"]'));
|
|
41
|
+
var indicator = list.querySelector(".ui-tab-indicator");
|
|
42
|
+
|
|
43
|
+
function enabled() { return all.filter(function (t) { return !t.disabled; }); }
|
|
44
|
+
function move() {
|
|
45
|
+
var t = all.filter(function (x) { return x.getAttribute("aria-selected") === "true"; })[0];
|
|
46
|
+
if (!indicator || !t) return;
|
|
47
|
+
indicator.style.setProperty("--x", t.offsetLeft + "px");
|
|
48
|
+
indicator.style.setProperty("--w", t.offsetWidth + "px");
|
|
49
|
+
}
|
|
50
|
+
function select(tab, focus) {
|
|
51
|
+
all.forEach(function (t) {
|
|
52
|
+
var on = t === tab;
|
|
53
|
+
t.setAttribute("aria-selected", String(on));
|
|
54
|
+
t.tabIndex = on ? 0 : -1;
|
|
55
|
+
var panel = document.getElementById(t.getAttribute("aria-controls"));
|
|
56
|
+
if (panel) panel.hidden = !on;
|
|
57
|
+
});
|
|
58
|
+
if (focus) tab.focus();
|
|
59
|
+
move();
|
|
60
|
+
// bubbles, so a page can listen once for every tab group in it
|
|
61
|
+
root.dispatchEvent(new CustomEvent("ui-tabchange", { bubbles: true, detail: { tab: tab } }));
|
|
62
|
+
}
|
|
63
|
+
all.forEach(function (t) {
|
|
64
|
+
t.addEventListener("click", function () { if (!t.disabled) select(t, false); });
|
|
65
|
+
t.addEventListener("keydown", function (e) {
|
|
66
|
+
var on = enabled(), i = on.indexOf(t), next = null;
|
|
67
|
+
if (e.key === "ArrowRight") next = on[(i + 1) % on.length];
|
|
68
|
+
else if (e.key === "ArrowLeft") next = on[(i - 1 + on.length) % on.length];
|
|
69
|
+
else if (e.key === "Home") next = on[0];
|
|
70
|
+
else if (e.key === "End") next = on[on.length - 1];
|
|
71
|
+
if (next) { e.preventDefault(); select(next, true); }
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
var first = all.filter(function (x) { return x.getAttribute("aria-selected") === "true"; })[0] || enabled()[0];
|
|
75
|
+
if (first) select(first, false);
|
|
76
|
+
// the indicator follows layout changes: fonts loading, the shape axis, a resize
|
|
77
|
+
if (window.ResizeObserver) new ResizeObserver(move).observe(list);
|
|
78
|
+
if (document.fonts && document.fonts.ready) document.fonts.ready.then(move);
|
|
79
|
+
root._uiTabs = { select: select, move: move };
|
|
80
|
+
return root._uiTabs;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
var lastOpener = new WeakMap();
|
|
84
|
+
function openDialog(dlg, opener) {
|
|
85
|
+
if (!dlg || dlg.open) return;
|
|
86
|
+
lastOpener.set(dlg, opener || document.activeElement);
|
|
87
|
+
dlg.showModal();
|
|
88
|
+
}
|
|
89
|
+
function closeDialog(dlg) { if (dlg && dlg.open) dlg.close(); }
|
|
90
|
+
function wireDialog(dlg) {
|
|
91
|
+
if (dlg._ui) return;
|
|
92
|
+
dlg._ui = true;
|
|
93
|
+
// a click whose target is the dialog element itself landed on the backdrop
|
|
94
|
+
dlg.addEventListener("click", function (e) { if (e.target === dlg) dlg.close(); });
|
|
95
|
+
dlg.addEventListener("close", function () {
|
|
96
|
+
var back = lastOpener.get(dlg);
|
|
97
|
+
if (back && back.focus && document.contains(back)) back.focus();
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// A line chart in an SVG that stretches to its box; strokes keep their width (non-scaling-stroke)
|
|
102
|
+
function chartSvg(values, w, h) {
|
|
103
|
+
var max = Math.max.apply(null, values), min = Math.min.apply(null, values), span = max - min || 1, pad = 6;
|
|
104
|
+
var pts = values.map(function (v, i) { return [i * w / (values.length - 1), pad + (h - 2 * pad) * (1 - (v - min) / span)]; });
|
|
105
|
+
var line = pts.map(function (p, i) { return (i ? "L" : "M") + p[0].toFixed(1) + " " + p[1].toFixed(1); }).join(" ");
|
|
106
|
+
var grid = [0.25, 0.5, 0.75].map(function (f) { return '<line class="ui-chart-grid" x1="0" x2="' + w + '" y1="' + h * f + '" y2="' + h * f + '" vector-effect="non-scaling-stroke"/>'; }).join("");
|
|
107
|
+
return '<svg viewBox="0 0 ' + w + " " + h + '" preserveAspectRatio="none" aria-hidden="true">' + grid +
|
|
108
|
+
'<path class="ui-chart-area" d="' + line + " L" + w + " " + h + " L0 " + h + ' Z"/>' +
|
|
109
|
+
'<path class="ui-chart-line" d="' + line + '" vector-effect="non-scaling-stroke"/></svg>';
|
|
110
|
+
}
|
|
111
|
+
function chart(el) {
|
|
112
|
+
var values = (el.getAttribute("data-values") || "").split(",").map(Number).filter(function (n) { return !isNaN(n); });
|
|
113
|
+
if (values.length < 2) return;
|
|
114
|
+
var labels = (el.getAttribute("data-labels") || "").split(",").filter(Boolean);
|
|
115
|
+
var name = el.getAttribute("data-label") || "Chart";
|
|
116
|
+
el.setAttribute("role", "img");
|
|
117
|
+
el.setAttribute("aria-label", name + ": " + values.map(function (v, i) { return (labels[i] ? labels[i] + " " : "") + v; }).join(", "));
|
|
118
|
+
el._uiChart = { values: values, labels: labels };
|
|
119
|
+
el.innerHTML = chartSvg(values, 600, 200) + '<span class="ui-chart-marker" aria-hidden="true" hidden></span><span class="ui-chart-tip" aria-hidden="true" hidden></span>' + (labels.length ? '<div class="ui-chart-axis" aria-hidden="true">' + labels.map(function (l) { return "<span>" + l + "</span>"; }).join("") + "</div>" : "");
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Hover: a marker and the nearest point's value. Pointer only; the values are already the chart's text alternative.
|
|
123
|
+
document.addEventListener("pointermove", function (e) {
|
|
124
|
+
var el = e.target.closest && e.target.closest(".ui-chart");
|
|
125
|
+
document.querySelectorAll(".ui-chart-tip:not([hidden])").forEach(function (t) { if (!el || !el.contains(t)) { t.hidden = true; t.previousElementSibling.hidden = true; } });
|
|
126
|
+
if (!el || !el._uiChart || e.pointerType === "touch" && e.type !== "pointermove") return;
|
|
127
|
+
var svg = el.querySelector("svg"), d = el._uiChart, r = svg.getBoundingClientRect();
|
|
128
|
+
if (e.clientX < r.left || e.clientX > r.right || e.clientY < r.top || e.clientY > r.bottom) return;
|
|
129
|
+
var i = Math.round((e.clientX - r.left) / r.width * (d.values.length - 1));
|
|
130
|
+
var max = Math.max.apply(null, d.values), min = Math.min.apply(null, d.values), span = max - min || 1;
|
|
131
|
+
var x = i / (d.values.length - 1) * r.width, y = (6 + (200 - 12) * (1 - (d.values[i] - min) / span)) / 200 * r.height;
|
|
132
|
+
var tip = el.querySelector(".ui-chart-tip"), mark = el.querySelector(".ui-chart-marker");
|
|
133
|
+
mark.hidden = false; tip.hidden = false;
|
|
134
|
+
mark.style.left = x + "px"; mark.style.height = r.height + "px";
|
|
135
|
+
tip.textContent = (d.labels[i] ? d.labels[i] + ": " : "") + d.values[i].toLocaleString();
|
|
136
|
+
tip.style.left = Math.max(0, Math.min(r.width - tip.offsetWidth, x - tip.offsetWidth / 2)) + "px";
|
|
137
|
+
tip.style.top = Math.max(0, y - 34) + "px";
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
// ── checkboxes: markup cannot set "indeterminate", so data-indeterminate does; data-ui-checkall="<name>" makes
|
|
141
|
+
// a box that checks every box of that name and shows mixed when some are checked
|
|
142
|
+
function syncCheckAll(all) {
|
|
143
|
+
var name = all.getAttribute("data-ui-checkall");
|
|
144
|
+
// a disabled box cannot be changed from here, so it does not count toward "all"
|
|
145
|
+
var boxes = Array.prototype.slice.call(document.querySelectorAll('input[type="checkbox"][name="' + name + '"]:not(:disabled)'));
|
|
146
|
+
var on = boxes.filter(function (b) { return b.checked; }).length;
|
|
147
|
+
all.checked = on === boxes.length && on > 0;
|
|
148
|
+
all.indeterminate = on > 0 && on < boxes.length;
|
|
149
|
+
}
|
|
150
|
+
document.addEventListener("change", function (e) {
|
|
151
|
+
var t = e.target;
|
|
152
|
+
if (!t.matches) return;
|
|
153
|
+
if (t.matches("[data-ui-checkall]")) {
|
|
154
|
+
document.querySelectorAll('input[type="checkbox"][name="' + t.getAttribute("data-ui-checkall") + '"]').forEach(function (b) { if (!b.disabled) b.checked = t.checked; });
|
|
155
|
+
syncCheckAll(t);
|
|
156
|
+
} else if (t.type === "checkbox" && t.name) {
|
|
157
|
+
document.querySelectorAll('[data-ui-checkall="' + t.name + '"]').forEach(syncCheckAll);
|
|
158
|
+
}
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
// ── slider: the filled part of the track follows the value
|
|
162
|
+
function slider(el) {
|
|
163
|
+
var min = +el.min || 0, max = el.max === "" ? 100 : +el.max, pct = (el.value - min) / (max - min || 1) * 100;
|
|
164
|
+
el.style.setProperty("--value", pct + "%");
|
|
165
|
+
var out = el.id && document.querySelector('output[for~="' + el.id + '"]');
|
|
166
|
+
if (out) out.textContent = (el.getAttribute("data-prefix") || "") + Number(el.value).toLocaleString() + (el.getAttribute("data-suffix") || "");
|
|
167
|
+
}
|
|
168
|
+
document.addEventListener("input", function (e) { if (e.target.classList && e.target.classList.contains("ui-slider")) slider(e.target); });
|
|
169
|
+
|
|
170
|
+
// ── button group: with data-ui-single on the group, pressing one releases the others
|
|
171
|
+
document.addEventListener("click", function (e) {
|
|
172
|
+
var b = e.target.closest && e.target.closest(".ui-btngroup-item[aria-pressed]");
|
|
173
|
+
if (!b || b.disabled) return;
|
|
174
|
+
var group = b.closest(".ui-btngroup");
|
|
175
|
+
if (group && group.hasAttribute("data-ui-single")) {
|
|
176
|
+
group.querySelectorAll(".ui-btngroup-item[aria-pressed]").forEach(function (x) { x.setAttribute("aria-pressed", String(x === b)); });
|
|
177
|
+
} else b.setAttribute("aria-pressed", String(b.getAttribute("aria-pressed") !== "true"));
|
|
178
|
+
group && group.dispatchEvent(new CustomEvent("ui-change", { bubbles: true, detail: { item: b } }));
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
// ── dropdown menu (the menu button pattern): Enter, Space or ArrowDown opens on the first item, ArrowUp on the last
|
|
182
|
+
var openMenuBtn = null;
|
|
183
|
+
function menuOf(btn) { return document.getElementById(btn.getAttribute("aria-controls")); }
|
|
184
|
+
function menuItems(menu) { return Array.prototype.slice.call(menu.querySelectorAll('[role="menuitem"]:not([aria-disabled="true"])')); }
|
|
185
|
+
function openMenu(btn, at) {
|
|
186
|
+
if (openMenuBtn && openMenuBtn !== btn) closeMenu(false);
|
|
187
|
+
var menu = menuOf(btn), wrap = btn.closest(".ui-menu-wrap");
|
|
188
|
+
if (!menu) return;
|
|
189
|
+
menu.hidden = false; btn.setAttribute("aria-expanded", "true"); openMenuBtn = btn;
|
|
190
|
+
// flip to the right edge when the menu would run off the screen
|
|
191
|
+
if (wrap) { wrap.removeAttribute("data-align"); if (menu.getBoundingClientRect().right > window.innerWidth - 8) wrap.setAttribute("data-align", "end"); }
|
|
192
|
+
var items = menuItems(menu);
|
|
193
|
+
if (items.length) (at === "last" ? items[items.length - 1] : items[0]).focus();
|
|
194
|
+
}
|
|
195
|
+
function closeMenu(focusBack) {
|
|
196
|
+
var btn = openMenuBtn;
|
|
197
|
+
if (!btn) return;
|
|
198
|
+
var menu = menuOf(btn);
|
|
199
|
+
if (menu) menu.hidden = true;
|
|
200
|
+
btn.setAttribute("aria-expanded", "false"); openMenuBtn = null;
|
|
201
|
+
if (focusBack) btn.focus();
|
|
202
|
+
}
|
|
203
|
+
document.addEventListener("click", function (e) {
|
|
204
|
+
var btn = e.target.closest && e.target.closest('[aria-haspopup="menu"]');
|
|
205
|
+
if (btn) { if (btn.getAttribute("aria-expanded") === "true") closeMenu(true); else openMenu(btn); return; }
|
|
206
|
+
var item = e.target.closest && e.target.closest('[role="menuitem"]');
|
|
207
|
+
if (item && openMenuBtn && menuOf(openMenuBtn).contains(item)) {
|
|
208
|
+
if (item.getAttribute("aria-disabled") === "true") return;
|
|
209
|
+
item.dispatchEvent(new CustomEvent("ui-select", { bubbles: true, detail: { item: item } }));
|
|
210
|
+
closeMenu(true); return;
|
|
211
|
+
}
|
|
212
|
+
if (openMenuBtn && !(menuOf(openMenuBtn) || document.body).contains(e.target)) closeMenu(false);
|
|
213
|
+
});
|
|
214
|
+
document.addEventListener("keydown", function (e) {
|
|
215
|
+
var btn = e.target.closest && e.target.closest('[aria-haspopup="menu"]');
|
|
216
|
+
if (btn && (e.key === "ArrowDown" || e.key === "ArrowUp")) { e.preventDefault(); openMenu(btn, e.key === "ArrowUp" ? "last" : "first"); return; }
|
|
217
|
+
if (!openMenuBtn) return;
|
|
218
|
+
var menu = menuOf(openMenuBtn);
|
|
219
|
+
if (!menu || !menu.contains(e.target)) return;
|
|
220
|
+
var items = menuItems(menu), i = items.indexOf(document.activeElement), next = null;
|
|
221
|
+
if (e.key === "ArrowDown") next = items[(i + 1) % items.length];
|
|
222
|
+
else if (e.key === "ArrowUp") next = items[(i - 1 + items.length) % items.length];
|
|
223
|
+
else if (e.key === "Home") next = items[0];
|
|
224
|
+
else if (e.key === "End") next = items[items.length - 1];
|
|
225
|
+
else if (e.key === "Escape") { e.preventDefault(); e.stopPropagation(); closeMenu(true); return; }
|
|
226
|
+
else if (e.key === "Tab") { closeMenu(false); return; }
|
|
227
|
+
else if (e.key.length === 1 && /\S/.test(e.key)) {
|
|
228
|
+
// type to jump: the next item starting with that letter
|
|
229
|
+
var k = e.key.toLowerCase(), order = items.slice(i + 1).concat(items.slice(0, i + 1));
|
|
230
|
+
next = order.filter(function (x) { return x.textContent.trim().toLowerCase().indexOf(k) === 0; })[0] || null;
|
|
231
|
+
}
|
|
232
|
+
if (next) { e.preventDefault(); next.focus(); }
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
// ── text editor: a toolbar of the browser's editing commands over a contenteditable area. execCommand is old but
|
|
236
|
+
// still supported everywhere and keeps undo working; a product that needs more should use a real editor library
|
|
237
|
+
function editor(root) {
|
|
238
|
+
if (root._uiEditor) return;
|
|
239
|
+
root._uiEditor = true;
|
|
240
|
+
var area = root.querySelector(".ui-editor-area"), tools = Array.prototype.slice.call(root.querySelectorAll(".ui-editor-tool"));
|
|
241
|
+
var count = root.querySelector("[data-ui-count]");
|
|
242
|
+
tools.forEach(function (t, i) { t.tabIndex = i === 0 ? 0 : -1; });
|
|
243
|
+
function sync() {
|
|
244
|
+
tools.forEach(function (t) {
|
|
245
|
+
var cmd = t.getAttribute("data-cmd"), on = false;
|
|
246
|
+
try { on = cmd === "formatBlock" ? /blockquote/i.test(document.queryCommandValue("formatBlock")) : document.queryCommandState(cmd); } catch (x) { /* not a state command */ }
|
|
247
|
+
if (t.hasAttribute("aria-pressed")) t.setAttribute("aria-pressed", String(!!on));
|
|
248
|
+
});
|
|
249
|
+
if (count) { var words = area.textContent.trim().split(/\s+/).filter(Boolean).length; count.textContent = words + (words === 1 ? " word" : " words"); }
|
|
250
|
+
}
|
|
251
|
+
root.querySelector(".ui-editor-toolbar").addEventListener("keydown", function (e) {
|
|
252
|
+
var i = tools.indexOf(document.activeElement), next = null;
|
|
253
|
+
if (e.key === "ArrowRight") next = tools[(i + 1) % tools.length];
|
|
254
|
+
else if (e.key === "ArrowLeft") next = tools[(i - 1 + tools.length) % tools.length];
|
|
255
|
+
else if (e.key === "Home") next = tools[0];
|
|
256
|
+
else if (e.key === "End") next = tools[tools.length - 1];
|
|
257
|
+
if (next) { e.preventDefault(); tools.forEach(function (t) { t.tabIndex = -1; }); next.tabIndex = 0; next.focus(); }
|
|
258
|
+
});
|
|
259
|
+
// mousedown keeps the selection in the text while a tool is clicked
|
|
260
|
+
tools.forEach(function (t) {
|
|
261
|
+
t.addEventListener("mousedown", function (e) { e.preventDefault(); });
|
|
262
|
+
t.addEventListener("click", function () {
|
|
263
|
+
var cmd = t.getAttribute("data-cmd");
|
|
264
|
+
area.focus();
|
|
265
|
+
if (cmd === "formatBlock") document.execCommand("formatBlock", false, /blockquote/i.test(document.queryCommandValue("formatBlock")) ? "p" : "blockquote");
|
|
266
|
+
else document.execCommand(cmd, false, null);
|
|
267
|
+
sync();
|
|
268
|
+
area.dispatchEvent(new Event("input", { bubbles: true }));
|
|
269
|
+
});
|
|
270
|
+
});
|
|
271
|
+
area.addEventListener("input", sync);
|
|
272
|
+
document.addEventListener("selectionchange", function () { if (area.contains(document.getSelection().anchorNode)) sync(); });
|
|
273
|
+
sync();
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// ── video player
|
|
277
|
+
var ICON = {
|
|
278
|
+
play: '<svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M7 4.5v15a1 1 0 0 0 1.5.86l12.5-7.5a1 1 0 0 0 0-1.72L8.5 3.64A1 1 0 0 0 7 4.5Z"/></svg>',
|
|
279
|
+
pause: '<svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><rect x="6" y="4" width="4" height="16" rx="1"/><rect x="14" y="4" width="4" height="16" rx="1"/></svg>',
|
|
280
|
+
sound: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M11 5 6 9H2v6h4l5 4V5Z"/><path d="M15.5 8.5a5 5 0 0 1 0 7M19 5a10 10 0 0 1 0 14"/></svg>',
|
|
281
|
+
muted: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M11 5 6 9H2v6h4l5 4V5Z"/><path d="m22 9-6 6M16 9l6 6"/></svg>'
|
|
282
|
+
};
|
|
283
|
+
function clock(s) { s = Math.max(0, Math.floor(s || 0)); return Math.floor(s / 60) + ":" + String(s % 60).padStart(2, "0"); }
|
|
284
|
+
function video(root) {
|
|
285
|
+
if (root._uiVideo) return;
|
|
286
|
+
root._uiVideo = true;
|
|
287
|
+
var v = root.querySelector("video"), play = root.querySelector("[data-ui-play]"), big = root.querySelector(".ui-video-big");
|
|
288
|
+
var mute = root.querySelector("[data-ui-mute]"), full = root.querySelector("[data-ui-full]"), seek = root.querySelector(".ui-slider"), time = root.querySelector(".ui-video-time");
|
|
289
|
+
var idle = null;
|
|
290
|
+
function wake() { root.removeAttribute("data-idle"); clearTimeout(idle); idle = setTimeout(function () { if (!root.contains(document.activeElement) || document.activeElement === v) root.setAttribute("data-idle", ""); }, 2000); }
|
|
291
|
+
function paint() {
|
|
292
|
+
var on = !v.paused;
|
|
293
|
+
root.toggleAttribute("data-playing", on);
|
|
294
|
+
if (play) { play.innerHTML = on ? ICON.pause : ICON.play; play.setAttribute("aria-label", on ? "Pause" : "Play"); }
|
|
295
|
+
if (mute) { mute.innerHTML = v.muted ? ICON.muted : ICON.sound; mute.setAttribute("aria-label", v.muted ? "Unmute" : "Mute"); }
|
|
296
|
+
if (seek && v.duration) {
|
|
297
|
+
seek.max = String(Math.round(v.duration * 10)); seek.value = String(Math.round(v.currentTime * 10));
|
|
298
|
+
seek.setAttribute("aria-valuetext", clock(v.currentTime) + " of " + clock(v.duration)); slider(seek);
|
|
299
|
+
}
|
|
300
|
+
if (time) time.textContent = clock(v.currentTime) + " / " + clock(v.duration);
|
|
301
|
+
}
|
|
302
|
+
function toggle() { if (v.paused) v.play(); else v.pause(); }
|
|
303
|
+
[play, big].forEach(function (b) { if (b) b.addEventListener("click", toggle); });
|
|
304
|
+
v.addEventListener("click", toggle);
|
|
305
|
+
if (mute) mute.addEventListener("click", function () { v.muted = !v.muted; paint(); });
|
|
306
|
+
if (full) full.addEventListener("click", function () { if (document.fullscreenElement) document.exitFullscreen(); else if (root.requestFullscreen) root.requestFullscreen(); });
|
|
307
|
+
if (seek) seek.addEventListener("input", function () { v.currentTime = seek.value / 10; });
|
|
308
|
+
["play", "pause", "timeupdate", "loadedmetadata", "volumechange", "ended"].forEach(function (n) { v.addEventListener(n, paint); });
|
|
309
|
+
root.addEventListener("pointermove", wake);
|
|
310
|
+
root.addEventListener("focusin", wake);
|
|
311
|
+
// k or Space plays and pauses, unless focus is on a control that uses the key itself
|
|
312
|
+
root.addEventListener("keydown", function (e) {
|
|
313
|
+
if (e.target.closest("button, input")) return;
|
|
314
|
+
if (e.key === " " || e.key === "k") { e.preventDefault(); toggle(); }
|
|
315
|
+
});
|
|
316
|
+
paint();
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// ── pagination: [data-ui-pagination data-page data-count] draws itself and moves; fires "ui-page" with the new page
|
|
320
|
+
function pageList(page, count) {
|
|
321
|
+
var want = [1, count, page - 1, page, page + 1].filter(function (n, i, a) { return n >= 1 && n <= count && a.indexOf(n) === i; }).sort(function (a, b) { return a - b; });
|
|
322
|
+
var out = []; want.forEach(function (n, i) { if (i && n - want[i - 1] > 1) out.push(null); out.push(n); }); return out;
|
|
323
|
+
}
|
|
324
|
+
var ARROW_L = '<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m15 18-6-6 6-6"/></svg>';
|
|
325
|
+
var ARROW_R = '<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m9 18 6-6-6-6"/></svg>';
|
|
326
|
+
function pagination(nav, focusPage) {
|
|
327
|
+
var page = +nav.getAttribute("data-page") || 1, count = +nav.getAttribute("data-count") || 1;
|
|
328
|
+
nav.innerHTML = '<button type="button" class="ui-btn ui-btn--sm" data-ui-to="' + (page - 1) + '"' + (page <= 1 ? " disabled" : "") + ">" + ARROW_L + "Previous</button>" +
|
|
329
|
+
'<ul class="ui-pagination-pages">' + pageList(page, count).map(function (n) {
|
|
330
|
+
return "<li>" + (n === null ? '<span class="ui-page-gap" aria-hidden="true">…</span>' : '<button type="button" class="ui-page" data-ui-to="' + n + '" aria-label="Page ' + n + '"' + (n === page ? ' aria-current="page"' : "") + ">" + n + "</button>") + "</li>";
|
|
331
|
+
}).join("") + "</ul>" +
|
|
332
|
+
'<span class="ui-pagination-info" aria-live="polite">Page ' + page + " of " + count + "</span>" +
|
|
333
|
+
'<button type="button" class="ui-btn ui-btn--sm" data-ui-to="' + (page + 1) + '"' + (page >= count ? " disabled" : "") + ">Next" + ARROW_R + "</button>";
|
|
334
|
+
if (focusPage) { var cur = nav.querySelector('[aria-current="page"]'); if (cur) cur.focus(); }
|
|
335
|
+
}
|
|
336
|
+
document.addEventListener("click", function (e) {
|
|
337
|
+
var b = e.target.closest && e.target.closest("[data-ui-pagination] [data-ui-to]");
|
|
338
|
+
if (!b || b.disabled) return;
|
|
339
|
+
var nav = b.closest("[data-ui-pagination]");
|
|
340
|
+
nav.setAttribute("data-page", b.getAttribute("data-ui-to"));
|
|
341
|
+
pagination(nav, true);
|
|
342
|
+
nav.dispatchEvent(new CustomEvent("ui-page", { bubbles: true, detail: { page: +nav.getAttribute("data-page") } }));
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
// ── calendar: [data-ui-calendar data-value="2026-04-18" data-events="2026-04-11,…"] draws a month and keeps
|
|
346
|
+
// the roving day; choosing a day fires "ui-date" with { value: "YYYY-MM-DD" }
|
|
347
|
+
var MONTH_NAMES = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];
|
|
348
|
+
var DOW = ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"], DOW_LONG = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
|
|
349
|
+
function iso(d) { return d.getFullYear() + "-" + String(d.getMonth() + 1).padStart(2, "0") + "-" + String(d.getDate()).padStart(2, "0"); }
|
|
350
|
+
function fromIso(s) { var m = /^(\d{4})-(\d{2})-(\d{2})$/.exec(s || ""); return m ? new Date(+m[1], +m[2] - 1, +m[3]) : null; }
|
|
351
|
+
function calendar(el, focusDay) {
|
|
352
|
+
var value = fromIso(el.getAttribute("data-value")), today = new Date(), events = (el.getAttribute("data-events") || "").split(",");
|
|
353
|
+
var cursor = fromIso(el.getAttribute("data-cursor")) || value || today;
|
|
354
|
+
el.setAttribute("data-cursor", iso(cursor));
|
|
355
|
+
var y = cursor.getFullYear(), mo = cursor.getMonth(), first = new Date(y, mo, 1), start = new Date(y, mo, 1 - first.getDay());
|
|
356
|
+
var id = el.id || (el.id = "cal-" + Math.random().toString(36).slice(2, 8));
|
|
357
|
+
var rows = "";
|
|
358
|
+
for (var w = 0; w < 6; w++) {
|
|
359
|
+
rows += "<tr>";
|
|
360
|
+
for (var d = 0; d < 7; d++) {
|
|
361
|
+
var day = new Date(start.getFullYear(), start.getMonth(), start.getDate() + w * 7 + d), s = iso(day);
|
|
362
|
+
rows += '<td role="gridcell"><button type="button" class="ui-day" data-date="' + s + '" tabindex="' + (s === iso(cursor) ? 0 : -1) + '"' +
|
|
363
|
+
' aria-label="' + DOW_LONG[day.getDay()] + ", " + MONTH_NAMES[day.getMonth()] + " " + day.getDate() + (events.indexOf(s) >= 0 ? ", has events" : "") + '"' +
|
|
364
|
+
(day.getMonth() !== mo ? " data-outside" : "") + (s === iso(today) ? ' aria-current="date"' : "") +
|
|
365
|
+
' aria-selected="' + (!!value && s === iso(value)) + '"' + (events.indexOf(s) >= 0 ? " data-event" : "") + ">" + day.getDate() + "</button></td>";
|
|
366
|
+
}
|
|
367
|
+
rows += "</tr>";
|
|
368
|
+
}
|
|
369
|
+
var NAV = function (dir, label, path) { return '<button type="button" class="ui-btn ui-btn--sm ui-btn--icon ui-btn--ghost" data-cal-step="' + dir + '" aria-label="' + label + '"><svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="' + path + '"/></svg></button>'; };
|
|
370
|
+
el.innerHTML = '<div class="ui-calendar-head">' + NAV(-1, "Previous month", "m15 18-6-6 6-6") +
|
|
371
|
+
'<h3 class="ui-calendar-title" id="' + id + '-t" aria-live="polite">' + MONTH_NAMES[mo] + " " + y + "</h3>" + NAV(1, "Next month", "m9 18 6-6-6-6") + "</div>" +
|
|
372
|
+
'<table role="grid" aria-labelledby="' + id + '-t"><thead><tr>' + DOW.map(function (x, i) { return '<th scope="col" abbr="' + DOW_LONG[i] + '">' + x + "</th>"; }).join("") + "</tr></thead><tbody>" + rows + "</tbody></table>";
|
|
373
|
+
if (focusDay) { var f = el.querySelector('[tabindex="0"]'); if (f) f.focus(); }
|
|
374
|
+
}
|
|
375
|
+
function calMove(el, days, months) {
|
|
376
|
+
var c = fromIso(el.getAttribute("data-cursor")), n = months ? new Date(c.getFullYear(), c.getMonth() + months, Math.min(c.getDate(), 28)) : new Date(c.getFullYear(), c.getMonth(), c.getDate() + days);
|
|
377
|
+
el.setAttribute("data-cursor", iso(n)); calendar(el, true);
|
|
378
|
+
}
|
|
379
|
+
document.addEventListener("click", function (e) {
|
|
380
|
+
var step = e.target.closest && e.target.closest("[data-ui-calendar] [data-cal-step]");
|
|
381
|
+
if (step) { var cal = step.closest("[data-ui-calendar]"), c = fromIso(cal.getAttribute("data-cursor")); cal.setAttribute("data-cursor", iso(new Date(c.getFullYear(), c.getMonth() + +step.getAttribute("data-cal-step"), 1))); calendar(cal, false); return; }
|
|
382
|
+
var day = e.target.closest && e.target.closest("[data-ui-calendar] .ui-day");
|
|
383
|
+
if (day && !day.disabled) {
|
|
384
|
+
var el = day.closest("[data-ui-calendar]");
|
|
385
|
+
el.setAttribute("data-value", day.getAttribute("data-date")); el.setAttribute("data-cursor", day.getAttribute("data-date")); calendar(el, true);
|
|
386
|
+
el.dispatchEvent(new CustomEvent("ui-date", { bubbles: true, detail: { value: day.getAttribute("data-date") } }));
|
|
387
|
+
}
|
|
388
|
+
});
|
|
389
|
+
document.addEventListener("keydown", function (e) {
|
|
390
|
+
var day = e.target.closest && e.target.closest("[data-ui-calendar] .ui-day");
|
|
391
|
+
if (!day) return;
|
|
392
|
+
var el = day.closest("[data-ui-calendar]"), k = e.key, c = fromIso(el.getAttribute("data-cursor"));
|
|
393
|
+
var moves = { ArrowLeft: [-1], ArrowRight: [1], ArrowUp: [-7], ArrowDown: [7], Home: [-c.getDay()], End: [6 - c.getDay()], PageUp: [0, -1], PageDown: [0, 1] };
|
|
394
|
+
if (moves[k]) { e.preventDefault(); calMove(el, moves[k][0], moves[k][1]); }
|
|
395
|
+
});
|
|
396
|
+
|
|
397
|
+
// ── date picker: [data-ui-datepicker] around a .ui-datepicker-btn and a hidden .ui-datepicker-pop with a calendar
|
|
398
|
+
function datepickerShow(dp, open) {
|
|
399
|
+
var pop = dp.querySelector(".ui-datepicker-pop"), btn = dp.querySelector(".ui-datepicker-btn");
|
|
400
|
+
pop.hidden = !open; btn.setAttribute("aria-expanded", String(open));
|
|
401
|
+
if (open) {
|
|
402
|
+
dp.removeAttribute("data-align");
|
|
403
|
+
if (pop.getBoundingClientRect().right > window.innerWidth - 8) dp.setAttribute("data-align", "end");
|
|
404
|
+
var cal = pop.querySelector("[data-ui-calendar]"); calendar(cal, true);
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
function datepickerLabel(dp) {
|
|
408
|
+
var v = fromIso(dp.querySelector("[data-ui-calendar]").getAttribute("data-value")), btn = dp.querySelector(".ui-datepicker-btn"), out = btn.querySelector("span");
|
|
409
|
+
if (v) { out.textContent = v.toLocaleDateString(undefined, { weekday: "short", month: "short", day: "numeric", year: "numeric" }); btn.removeAttribute("data-empty"); }
|
|
410
|
+
}
|
|
411
|
+
document.addEventListener("click", function (e) {
|
|
412
|
+
var btn = e.target.closest && e.target.closest("[data-ui-datepicker] .ui-datepicker-btn");
|
|
413
|
+
if (btn) { var dp = btn.closest("[data-ui-datepicker]"); datepickerShow(dp, dp.querySelector(".ui-datepicker-pop").hidden); return; }
|
|
414
|
+
document.querySelectorAll("[data-ui-datepicker]").forEach(function (dp) { if (!dp.contains(e.target) && !dp.querySelector(".ui-datepicker-pop").hidden) datepickerShow(dp, false); });
|
|
415
|
+
});
|
|
416
|
+
document.addEventListener("ui-date", function (e) {
|
|
417
|
+
var dp = e.target.closest && e.target.closest("[data-ui-datepicker]");
|
|
418
|
+
if (!dp) return;
|
|
419
|
+
datepickerLabel(dp); datepickerShow(dp, false); dp.querySelector(".ui-datepicker-btn").focus();
|
|
420
|
+
});
|
|
421
|
+
document.addEventListener("keydown", function (e) {
|
|
422
|
+
if (e.key !== "Escape") return;
|
|
423
|
+
var dp = e.target.closest && e.target.closest("[data-ui-datepicker]");
|
|
424
|
+
if (dp && !dp.querySelector(".ui-datepicker-pop").hidden) { e.preventDefault(); e.stopPropagation(); datepickerShow(dp, false); dp.querySelector(".ui-datepicker-btn").focus(); }
|
|
425
|
+
});
|
|
426
|
+
|
|
427
|
+
// ── copy: [data-ui-copy="#id"] copies that element's text, then says so on the button for two seconds
|
|
428
|
+
document.addEventListener("click", function (e) {
|
|
429
|
+
var b = e.target.closest && e.target.closest("[data-ui-copy]");
|
|
430
|
+
if (!b) return;
|
|
431
|
+
var src = document.querySelector(b.getAttribute("data-ui-copy")), label = b.querySelector("span") || b, was = label.textContent;
|
|
432
|
+
if (!src || !navigator.clipboard) return;
|
|
433
|
+
navigator.clipboard.writeText(src.textContent).then(function () { label.textContent = "Copied"; setTimeout(function () { label.textContent = was; }, 2000); },
|
|
434
|
+
function () { label.textContent = "Copy failed"; setTimeout(function () { label.textContent = was; }, 2000); });
|
|
435
|
+
});
|
|
436
|
+
|
|
437
|
+
// ── color picker: [data-ui-colorpick] keeps its swatches (a radiogroup), the native picker and the hex field in step;
|
|
438
|
+
// fires "ui-color" with { value: "#rrggbb" }
|
|
439
|
+
function colorpickSet(root, hex, from) {
|
|
440
|
+
hex = hex.toLowerCase();
|
|
441
|
+
root.querySelectorAll(".ui-swatch").forEach(function (s) { var on = s.getAttribute("data-color").toLowerCase() === hex; s.setAttribute("aria-checked", String(on)); s.tabIndex = on ? 0 : -1; });
|
|
442
|
+
if (!root.querySelector('.ui-swatch[aria-checked="true"]')) { var f = root.querySelector(".ui-swatch"); if (f) f.tabIndex = 0; }
|
|
443
|
+
var native = root.querySelector('input[type="color"]'), text = root.querySelector('input[type="text"]');
|
|
444
|
+
if (native && from !== native) native.value = hex;
|
|
445
|
+
if (text && from !== text) text.value = hex;
|
|
446
|
+
root.dispatchEvent(new CustomEvent("ui-color", { bubbles: true, detail: { value: hex } }));
|
|
447
|
+
}
|
|
448
|
+
document.addEventListener("click", function (e) {
|
|
449
|
+
var s = e.target.closest && e.target.closest("[data-ui-colorpick] .ui-swatch");
|
|
450
|
+
if (s) colorpickSet(s.closest("[data-ui-colorpick]"), s.getAttribute("data-color"), s);
|
|
451
|
+
});
|
|
452
|
+
document.addEventListener("input", function (e) {
|
|
453
|
+
var root = e.target.closest && e.target.closest("[data-ui-colorpick]");
|
|
454
|
+
if (!root || e.target.tagName !== "INPUT") return;
|
|
455
|
+
var v = e.target.value.trim();
|
|
456
|
+
if (e.target.type === "text") { if (/^#?[0-9a-f]{6}$/i.test(v)) colorpickSet(root, v[0] === "#" ? v : "#" + v, e.target); e.target.setAttribute("aria-invalid", String(!/^#?[0-9a-f]{6}$/i.test(v))); }
|
|
457
|
+
else colorpickSet(root, v, e.target);
|
|
458
|
+
});
|
|
459
|
+
document.addEventListener("keydown", function (e) {
|
|
460
|
+
var s = e.target.closest && e.target.closest("[data-ui-colorpick] .ui-swatch");
|
|
461
|
+
if (!s) return;
|
|
462
|
+
var all = Array.prototype.slice.call(s.parentElement.querySelectorAll(".ui-swatch")), i = all.indexOf(s), n = null;
|
|
463
|
+
if (e.key === "ArrowRight" || e.key === "ArrowDown") n = all[(i + 1) % all.length];
|
|
464
|
+
else if (e.key === "ArrowLeft" || e.key === "ArrowUp") n = all[(i - 1 + all.length) % all.length];
|
|
465
|
+
if (n) { e.preventDefault(); colorpickSet(s.closest("[data-ui-colorpick]"), n.getAttribute("data-color"), n); n.focus(); }
|
|
466
|
+
});
|
|
467
|
+
|
|
468
|
+
// ── command menu: a <dialog data-ui-command> with an input (role="combobox") over a listbox of
|
|
469
|
+
// [role="option"][data-keywords]; ⌘K or Ctrl+K opens the first one on the page; Enter fires "ui-command"
|
|
470
|
+
function commandFilter(dlg) {
|
|
471
|
+
var q = dlg.querySelector('[role="combobox"]').value.trim().toLowerCase(), shown = 0;
|
|
472
|
+
dlg.querySelectorAll('[role="option"]').forEach(function (o) { var hit = !q || (o.textContent + " " + (o.getAttribute("data-keywords") || "")).toLowerCase().indexOf(q) >= 0; o.hidden = !hit; if (hit) shown++; });
|
|
473
|
+
dlg.querySelectorAll('[role="group"]').forEach(function (g) { g.hidden = !g.querySelector('[role="option"]:not([hidden])'); });
|
|
474
|
+
var empty = dlg.querySelector(".ui-command-empty"); if (empty) empty.hidden = shown > 0;
|
|
475
|
+
commandActive(dlg, dlg.querySelector('[role="option"]:not([hidden])'));
|
|
476
|
+
}
|
|
477
|
+
function commandActive(dlg, opt) {
|
|
478
|
+
var input = dlg.querySelector('[role="combobox"]');
|
|
479
|
+
dlg.querySelectorAll('[role="option"]').forEach(function (o) { o.setAttribute("aria-selected", String(o === opt)); });
|
|
480
|
+
if (opt) { input.setAttribute("aria-activedescendant", opt.id); opt.scrollIntoView({ block: "nearest" }); } else input.removeAttribute("aria-activedescendant");
|
|
481
|
+
}
|
|
482
|
+
function commandRun(dlg, opt) { if (!opt) return; opt.dispatchEvent(new CustomEvent("ui-command", { bubbles: true, detail: { item: opt } })); dlg.close(); }
|
|
483
|
+
function commandOpen(dlg, opener) { var input = dlg.querySelector('[role="combobox"]'); input.value = ""; commandFilter(dlg); openDialog(dlg, opener); input.focus(); }
|
|
484
|
+
document.addEventListener("input", function (e) { var dlg = e.target.closest && e.target.closest("[data-ui-command]"); if (dlg && e.target.getAttribute("role") === "combobox") commandFilter(dlg); });
|
|
485
|
+
document.addEventListener("keydown", function (e) {
|
|
486
|
+
if ((e.metaKey || e.ctrlKey) && (e.key === "k" || e.key === "K")) { var d = document.querySelector("dialog[data-ui-command]"); if (d && !d.open) { e.preventDefault(); wireDialog(d); commandOpen(d, document.activeElement); } return; }
|
|
487
|
+
var dlg = e.target.closest && e.target.closest("[data-ui-command]");
|
|
488
|
+
if (!dlg || e.target.getAttribute("role") !== "combobox") return;
|
|
489
|
+
var opts = Array.prototype.slice.call(dlg.querySelectorAll('[role="option"]:not([hidden])')), cur = dlg.querySelector('[role="option"][aria-selected="true"]'), i = opts.indexOf(cur);
|
|
490
|
+
if (e.key === "ArrowDown") { e.preventDefault(); commandActive(dlg, opts[(i + 1) % opts.length]); }
|
|
491
|
+
else if (e.key === "ArrowUp") { e.preventDefault(); commandActive(dlg, opts[(i - 1 + opts.length) % opts.length]); }
|
|
492
|
+
else if (e.key === "Enter") { e.preventDefault(); commandRun(dlg, cur); }
|
|
493
|
+
});
|
|
494
|
+
document.addEventListener("click", function (e) {
|
|
495
|
+
var open = e.target.closest && e.target.closest("[data-ui-command-open]");
|
|
496
|
+
if (open) { var d = document.getElementById(open.getAttribute("data-ui-command-open")); if (d) { wireDialog(d); commandOpen(d, open); } return; }
|
|
497
|
+
var opt = e.target.closest && e.target.closest("[data-ui-command] [role='option']");
|
|
498
|
+
if (opt) commandRun(opt.closest("[data-ui-command]"), opt);
|
|
499
|
+
});
|
|
500
|
+
document.addEventListener("mousemove", function (e) { var opt = e.target.closest && e.target.closest("[data-ui-command] [role='option']"); if (opt && opt.getAttribute("aria-selected") !== "true") commandActive(opt.closest("[data-ui-command]"), opt); });
|
|
501
|
+
|
|
502
|
+
// ── file drop zone: [data-ui-dropzone] around a label.ui-dropzone (with its file input) and a ul.ui-files;
|
|
503
|
+
// lists what was chosen or dropped (nothing is uploaded) and fires "ui-files" with the File list
|
|
504
|
+
function fileSize(n) { return n < 1024 ? n + " B" : n < 1048576 ? (n / 1024).toFixed(1) + " KB" : (n / 1048576).toFixed(1) + " MB"; }
|
|
505
|
+
var FILE_ICON = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7z"/><path d="M14 2v4a2 2 0 0 0 2 2h4"/></svg>';
|
|
506
|
+
var X_ICON = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" aria-hidden="true"><path d="M18 6 6 18M6 6l12 12"/></svg>';
|
|
507
|
+
function dropzoneAdd(root, files) {
|
|
508
|
+
var list = root.querySelector(".ui-files");
|
|
509
|
+
Array.prototype.forEach.call(files, function (f) {
|
|
510
|
+
var li = document.createElement("li"); li.className = "ui-file";
|
|
511
|
+
li.innerHTML = FILE_ICON + '<span class="ui-file-name"></span><span class="ui-file-size">' + fileSize(f.size) + '</span><button type="button" class="ui-tag-remove" data-ui-file-remove></button>';
|
|
512
|
+
li.querySelector(".ui-file-name").textContent = f.name;
|
|
513
|
+
li.querySelector("button").setAttribute("aria-label", "Remove " + f.name); li.querySelector("button").innerHTML = X_ICON;
|
|
514
|
+
list.appendChild(li);
|
|
515
|
+
});
|
|
516
|
+
root.dispatchEvent(new CustomEvent("ui-files", { bubbles: true, detail: { files: files } }));
|
|
517
|
+
}
|
|
518
|
+
document.addEventListener("change", function (e) { var root = e.target.closest && e.target.closest("[data-ui-dropzone]"); if (root && e.target.type === "file") { dropzoneAdd(root, e.target.files); e.target.value = ""; } });
|
|
519
|
+
["dragenter", "dragover"].forEach(function (n) { document.addEventListener(n, function (e) { var z = e.target.closest && e.target.closest(".ui-dropzone"); if (z && z.closest("[data-ui-dropzone]")) { e.preventDefault(); z.classList.add("is-drag"); } }); });
|
|
520
|
+
["dragleave", "drop"].forEach(function (n) { document.addEventListener(n, function (e) { var z = e.target.closest && e.target.closest(".ui-dropzone"); if (z) { z.classList.remove("is-drag"); if (n === "drop" && z.closest("[data-ui-dropzone]")) { e.preventDefault(); dropzoneAdd(z.closest("[data-ui-dropzone]"), e.dataTransfer.files); } } }); });
|
|
521
|
+
document.addEventListener("click", function (e) {
|
|
522
|
+
var x = e.target.closest && e.target.closest("[data-ui-file-remove]"); if (!x) return;
|
|
523
|
+
var li = x.closest(".ui-file"), next = li.nextElementSibling || li.previousElementSibling, root = li.closest("[data-ui-dropzone]");
|
|
524
|
+
li.remove(); (next ? next.querySelector("button") : root.querySelector("input[type=file]")).focus();
|
|
525
|
+
});
|
|
526
|
+
|
|
527
|
+
// ── chips: .ui-chip[aria-pressed] toggles; fires "ui-change" from the chip
|
|
528
|
+
document.addEventListener("click", function (e) {
|
|
529
|
+
var c = e.target.closest && e.target.closest(".ui-chip[aria-pressed]");
|
|
530
|
+
if (!c || c.disabled) return;
|
|
531
|
+
c.setAttribute("aria-pressed", String(c.getAttribute("aria-pressed") !== "true"));
|
|
532
|
+
c.dispatchEvent(new CustomEvent("ui-change", { bubbles: true, detail: { item: c } }));
|
|
533
|
+
});
|
|
534
|
+
|
|
535
|
+
// ── chat: [data-ui-chat] sends from its composer: Enter sends, Shift+Enter adds a line; fires "ui-send" with the text
|
|
536
|
+
function chatSend(root) {
|
|
537
|
+
var box = root.querySelector(".ui-chat-compose textarea"), text = box.value.trim();
|
|
538
|
+
if (!text) return;
|
|
539
|
+
var li = document.createElement("li"); li.className = "ui-msg ui-msg--out";
|
|
540
|
+
li.innerHTML = '<div class="ui-msg-bubble"></div><span class="ui-msg-meta">You · just now</span>';
|
|
541
|
+
li.querySelector(".ui-msg-bubble").textContent = text;
|
|
542
|
+
var log = root.querySelector(".ui-chat-log"); log.appendChild(li); log.scrollTop = log.scrollHeight;
|
|
543
|
+
box.value = ""; box.focus();
|
|
544
|
+
root.dispatchEvent(new CustomEvent("ui-send", { bubbles: true, detail: { text: text } }));
|
|
545
|
+
}
|
|
546
|
+
document.addEventListener("keydown", function (e) {
|
|
547
|
+
if (e.key !== "Enter" || e.shiftKey || e.isComposing) return;
|
|
548
|
+
var root = e.target.closest && e.target.closest("[data-ui-chat]");
|
|
549
|
+
if (root && e.target.tagName === "TEXTAREA") { e.preventDefault(); chatSend(root); }
|
|
550
|
+
});
|
|
551
|
+
document.addEventListener("click", function (e) { var b = e.target.closest && e.target.closest("[data-ui-chat] [data-ui-send]"); if (b) chatSend(b.closest("[data-ui-chat]")); });
|
|
552
|
+
|
|
553
|
+
// ── tree: [role="tree"].ui-tree, one roving tab stop; fires "ui-select" from the chosen item
|
|
554
|
+
function treeVisible(tree) { return Array.prototype.slice.call(tree.querySelectorAll('[role="treeitem"]')).filter(function (t) { var p = t.parentElement.closest('[role="treeitem"]'); while (p) { if (p.getAttribute("aria-expanded") === "false") return false; p = p.parentElement.closest('[role="treeitem"]'); } return true; }); }
|
|
555
|
+
function treeFocus(tree, item) { tree.querySelectorAll('[role="treeitem"]').forEach(function (t) { t.tabIndex = t === item ? 0 : -1; }); item.focus(); }
|
|
556
|
+
function treeSelect(tree, item) { tree.querySelectorAll('[role="treeitem"]').forEach(function (t) { t.setAttribute("aria-selected", String(t === item)); }); item.dispatchEvent(new CustomEvent("ui-select", { bubbles: true, detail: { item: item } })); }
|
|
557
|
+
document.addEventListener("click", function (e) {
|
|
558
|
+
var row = e.target.closest && e.target.closest('.ui-tree .ui-tree-row');
|
|
559
|
+
if (!row) return;
|
|
560
|
+
var item = row.parentElement, tree = item.closest('[role="tree"]');
|
|
561
|
+
if (item.hasAttribute("aria-expanded")) item.setAttribute("aria-expanded", String(item.getAttribute("aria-expanded") !== "true"));
|
|
562
|
+
treeSelect(tree, item); treeFocus(tree, item);
|
|
563
|
+
});
|
|
564
|
+
document.addEventListener("keydown", function (e) {
|
|
565
|
+
var item = e.target.closest && e.target.matches && e.target.matches('.ui-tree [role="treeitem"]') ? e.target : null;
|
|
566
|
+
if (!item) return;
|
|
567
|
+
var tree = item.closest('[role="tree"]'), vis = treeVisible(tree), i = vis.indexOf(item), exp = item.getAttribute("aria-expanded"), n = null;
|
|
568
|
+
if (e.key === "ArrowDown") n = vis[i + 1];
|
|
569
|
+
else if (e.key === "ArrowUp") n = vis[i - 1];
|
|
570
|
+
else if (e.key === "Home") n = vis[0];
|
|
571
|
+
else if (e.key === "End") n = vis[vis.length - 1];
|
|
572
|
+
else if (e.key === "ArrowRight") { if (exp === "false") item.setAttribute("aria-expanded", "true"); else if (exp === "true") n = item.querySelector('[role="treeitem"]'); }
|
|
573
|
+
else if (e.key === "ArrowLeft") { if (exp === "true") item.setAttribute("aria-expanded", "false"); else n = item.parentElement.closest('[role="treeitem"]'); }
|
|
574
|
+
else if (e.key === "Enter" || e.key === " ") { treeSelect(tree, item); if (exp) item.setAttribute("aria-expanded", String(exp !== "true")); }
|
|
575
|
+
else return;
|
|
576
|
+
e.preventDefault();
|
|
577
|
+
if (n) treeFocus(tree, n);
|
|
578
|
+
});
|
|
579
|
+
function tree(t) { var items = t.querySelectorAll('[role="treeitem"]'), sel = t.querySelector('[aria-selected="true"]') || items[0]; items.forEach(function (x) { x.tabIndex = x === sel ? 0 : -1; }); }
|
|
580
|
+
|
|
581
|
+
// ── site header: on a narrow header the links fold into a menu; its button opens and closes the panel
|
|
582
|
+
document.addEventListener("click", function (e) {
|
|
583
|
+
var b = e.target.closest && e.target.closest(".ui-sitenav-toggle");
|
|
584
|
+
if (!b) return;
|
|
585
|
+
var panel = document.getElementById(b.getAttribute("aria-controls")), open = b.getAttribute("aria-expanded") !== "true";
|
|
586
|
+
b.setAttribute("aria-expanded", String(open)); b.setAttribute("aria-label", open ? "Close menu" : "Open menu");
|
|
587
|
+
if (panel) { panel.hidden = !open; if (open) { var first = panel.querySelector("a, button"); if (first) first.focus(); } }
|
|
588
|
+
});
|
|
589
|
+
document.addEventListener("keydown", function (e) {
|
|
590
|
+
if (e.key !== "Escape") return;
|
|
591
|
+
var nav = e.target.closest && e.target.closest(".ui-sitenav"), b = nav && nav.querySelector('.ui-sitenav-toggle[aria-expanded="true"]');
|
|
592
|
+
// claim the key, so a full-screen view or dialog around the header stays open
|
|
593
|
+
if (b) { e.preventDefault(); b.click(); b.focus(); }
|
|
594
|
+
});
|
|
595
|
+
|
|
596
|
+
// ── banner: [data-ui-banner-close] hides its banner and fires "ui-dismiss"; focus moves to the next focusable
|
|
597
|
+
document.addEventListener("click", function (e) {
|
|
598
|
+
var b = e.target.closest && e.target.closest("[data-ui-banner-close]");
|
|
599
|
+
if (!b) return;
|
|
600
|
+
var banner = b.closest(".ui-banner"); if (!banner) return;
|
|
601
|
+
banner.hidden = true; banner.dispatchEvent(new CustomEvent("ui-dismiss", { bubbles: true }));
|
|
602
|
+
var next = banner.nextElementSibling && banner.nextElementSibling.querySelector("a, button, input, select, textarea, [tabindex]");
|
|
603
|
+
if (next) next.focus();
|
|
604
|
+
});
|
|
605
|
+
|
|
606
|
+
// ── annotations: a pin ([data-annot="n"]) and its note ([data-annot-note="n"]) light up together
|
|
607
|
+
function annotShow(root, n) {
|
|
608
|
+
root.querySelectorAll("[data-annot]").forEach(function (p) { p.setAttribute("aria-pressed", String(p.getAttribute("data-annot") === n)); });
|
|
609
|
+
root.querySelectorAll("[data-annot-note]").forEach(function (li) { li.toggleAttribute("data-active", li.getAttribute("data-annot-note") === n); });
|
|
610
|
+
}
|
|
611
|
+
document.addEventListener("click", function (e) {
|
|
612
|
+
var pin = e.target.closest && e.target.closest(".ui-annot [data-annot]");
|
|
613
|
+
if (pin) { var root = pin.closest(".ui-annot"), on = pin.getAttribute("aria-pressed") !== "true"; annotShow(root, on ? pin.getAttribute("data-annot") : ""); }
|
|
614
|
+
});
|
|
615
|
+
|
|
616
|
+
// ── sidebar (ui-sidebar): a rail when wide, a drawer when its app shell is under 720px (components.css) ──
|
|
617
|
+
// data-ui-sidebar on the <aside>; buttons with aria-controls="<its id>" and data-ui-sidebar-toggle open and
|
|
618
|
+
// close it; a group's button (data-ui-sidebar-group) opens its body in place.
|
|
619
|
+
var SIDEBAR_NARROW = 720; // keep in step with the @container query in components.css
|
|
620
|
+
function sidebarNarrow(sb) { var app = sb.closest(".ui-app"); return (app ? app.getBoundingClientRect().width : window.innerWidth) <= SIDEBAR_NARROW; }
|
|
621
|
+
function sidebarSet(sb, open, focusBack) {
|
|
622
|
+
sb.setAttribute("data-state", open ? "expanded" : "collapsed");
|
|
623
|
+
Array.prototype.forEach.call(document.querySelectorAll('[data-ui-sidebar-toggle][aria-controls="' + sb.id + '"]'), function (b) {
|
|
624
|
+
b.setAttribute("aria-expanded", String(open));
|
|
625
|
+
if (b.classList.contains("ui-sidebar-toggle")) b.setAttribute("aria-label", sidebarNarrow(sb) ? "Close menu" : open ? "Collapse sidebar" : "Expand sidebar");
|
|
626
|
+
});
|
|
627
|
+
if (!open && focusBack) { var opener = document.querySelector('.ui-sidebar-open[aria-controls="' + sb.id + '"]'); if (opener && opener.offsetParent) opener.focus(); }
|
|
628
|
+
// a drawer that opens takes focus, so Tab moves through it and Escape closes it
|
|
629
|
+
if (open && focusBack && sidebarNarrow(sb)) { var first = sb.querySelector(".ui-sidebar-toggle, .ui-sidebar-item"); if (first) first.focus(); }
|
|
630
|
+
}
|
|
631
|
+
function sidebar(sb) { if (sidebarNarrow(sb)) sidebarSet(sb, false); else sidebarSet(sb, sb.getAttribute("data-state") !== "collapsed"); }
|
|
632
|
+
document.addEventListener("click", function (e) {
|
|
633
|
+
var t = e.target.closest && e.target.closest("[data-ui-sidebar-toggle]");
|
|
634
|
+
if (t) { var sb = document.getElementById(t.getAttribute("aria-controls")); if (sb) sidebarSet(sb, sb.getAttribute("data-state") !== "expanded", true); return; }
|
|
635
|
+
var g = e.target.closest && e.target.closest("[data-ui-sidebar-group]");
|
|
636
|
+
if (g) {
|
|
637
|
+
var bar = g.closest("[data-ui-sidebar]"), body = document.getElementById(g.getAttribute("aria-controls"));
|
|
638
|
+
if (bar && bar.getAttribute("data-state") === "collapsed" && !sidebarNarrow(bar)) { sidebarSet(bar, true); g.setAttribute("aria-expanded", "true"); if (body) body.hidden = false; return; }
|
|
639
|
+
var open = g.getAttribute("aria-expanded") !== "true"; g.setAttribute("aria-expanded", String(open)); if (body) body.hidden = !open; return;
|
|
640
|
+
}
|
|
641
|
+
// narrow: a tap outside the open drawer closes it
|
|
642
|
+
Array.prototype.forEach.call(document.querySelectorAll('[data-ui-sidebar][data-state="expanded"]'), function (sb) {
|
|
643
|
+
if (sidebarNarrow(sb) && !sb.contains(e.target)) sidebarSet(sb, false);
|
|
644
|
+
});
|
|
645
|
+
});
|
|
646
|
+
document.addEventListener("keydown", function (e) {
|
|
647
|
+
if (e.key !== "Escape" || e.defaultPrevented) return;
|
|
648
|
+
Array.prototype.forEach.call(document.querySelectorAll('[data-ui-sidebar][data-state="expanded"]'), function (sb) {
|
|
649
|
+
if (sidebarNarrow(sb)) { sidebarSet(sb, false, true); e.preventDefault(); }
|
|
650
|
+
});
|
|
651
|
+
});
|
|
652
|
+
|
|
653
|
+
function init(scope) {
|
|
654
|
+
scope = scope || document;
|
|
655
|
+
Array.prototype.forEach.call(scope.querySelectorAll("[data-ui-tabs]"), tabs);
|
|
656
|
+
Array.prototype.forEach.call(scope.querySelectorAll("[data-ui-sidebar]"), sidebar);
|
|
657
|
+
Array.prototype.forEach.call(scope.querySelectorAll("dialog.ui-dialog"), wireDialog);
|
|
658
|
+
Array.prototype.forEach.call(scope.querySelectorAll("[data-ui-chart]"), chart);
|
|
659
|
+
Array.prototype.forEach.call(scope.querySelectorAll("[data-indeterminate]"), function (b) { b.indeterminate = true; });
|
|
660
|
+
Array.prototype.forEach.call(scope.querySelectorAll("[data-ui-checkall]"), syncCheckAll);
|
|
661
|
+
Array.prototype.forEach.call(scope.querySelectorAll(".ui-slider"), slider);
|
|
662
|
+
Array.prototype.forEach.call(scope.querySelectorAll("[data-ui-editor]"), editor);
|
|
663
|
+
Array.prototype.forEach.call(scope.querySelectorAll("[data-ui-video]"), video);
|
|
664
|
+
Array.prototype.forEach.call(scope.querySelectorAll("[data-ui-pagination]"), function (n) { pagination(n, false); });
|
|
665
|
+
Array.prototype.forEach.call(scope.querySelectorAll("[data-ui-calendar]"), function (n) { calendar(n, false); });
|
|
666
|
+
Array.prototype.forEach.call(scope.querySelectorAll("[data-ui-datepicker]"), datepickerLabel);
|
|
667
|
+
Array.prototype.forEach.call(scope.querySelectorAll(".ui-tree[role='tree']"), tree);
|
|
668
|
+
Array.prototype.forEach.call(scope.querySelectorAll("[data-ui-colorpick]"), function (n) { var v = n.querySelector('input[type="color"]'); if (v) colorpickSet(n, v.value); });
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
// Escape dismisses any tooltip that is showing; leaving the wrap re-arms it
|
|
672
|
+
document.addEventListener("keydown", function (e) {
|
|
673
|
+
if (e.key !== "Escape") return;
|
|
674
|
+
Array.prototype.forEach.call(document.querySelectorAll(".ui-tooltip-wrap:hover, .ui-tooltip-wrap:focus-within"), function (w) { w.classList.add("is-dismissed"); });
|
|
675
|
+
});
|
|
676
|
+
function rearm(e) { var w = e.target.closest && e.target.closest(".ui-tooltip-wrap"); if (w && !w.contains(e.relatedTarget)) w.classList.remove("is-dismissed"); }
|
|
677
|
+
document.addEventListener("mouseout", rearm);
|
|
678
|
+
document.addEventListener("focusout", rearm);
|
|
679
|
+
|
|
680
|
+
function dismissTag(btn) {
|
|
681
|
+
var tag = btn.closest(".ui-tag");
|
|
682
|
+
if (!tag) return;
|
|
683
|
+
var list = tag.parentElement, all = Array.prototype.slice.call(list.querySelectorAll("[data-ui-dismiss]")), i = all.indexOf(btn);
|
|
684
|
+
var next = all[i + 1] || all[i - 1] || null;
|
|
685
|
+
tag.dispatchEvent(new CustomEvent("ui-dismiss", { bubbles: true }));
|
|
686
|
+
tag.remove();
|
|
687
|
+
if (next) next.focus();
|
|
688
|
+
else { if (!list.hasAttribute("tabindex")) list.setAttribute("tabindex", "-1"); list.focus(); }
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
document.addEventListener("click", function (e) {
|
|
692
|
+
var dismiss = e.target.closest && e.target.closest("[data-ui-dismiss]");
|
|
693
|
+
if (dismiss) { dismissTag(dismiss); return; }
|
|
694
|
+
var open = e.target.closest && e.target.closest("[data-ui-open]");
|
|
695
|
+
if (open) { var d = document.getElementById(open.getAttribute("data-ui-open")); if (d) { wireDialog(d); openDialog(d, open); } return; }
|
|
696
|
+
var close = e.target.closest && e.target.closest("[data-ui-close]");
|
|
697
|
+
if (close) closeDialog(close.closest("dialog"));
|
|
698
|
+
});
|
|
699
|
+
|
|
700
|
+
window.UI = { init: init, tabs: tabs, chart: chart, openDialog: openDialog, closeDialog: closeDialog };
|
|
701
|
+
if (document.readyState === "loading") document.addEventListener("DOMContentLoaded", function () { init(); });
|
|
702
|
+
else init();
|
|
703
|
+
})();
|