@wpmoo/ui 1.0.0-rc.9 → 1.1.0-dev.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.
Files changed (96) hide show
  1. package/ASSET_LICENSE.md +1 -1
  2. package/LICENSE +28 -0
  3. package/README.md +38 -3
  4. package/certification.json +3 -1
  5. package/dist/assets/css/moo-ui.css +436 -25
  6. package/dist/assets/css/moo-ui.min.css +10 -31
  7. package/dist/assets/css/moo.css +428 -24
  8. package/dist/assets/css/moo.min.css +10 -32
  9. package/dist/js/chart.js +23 -21
  10. package/dist/js/chart.min.js +5 -5
  11. package/dist/js/combobox.js +18 -65
  12. package/dist/js/context-menu.js +16 -68
  13. package/dist/js/datatable.js +38 -21
  14. package/dist/js/datepicker.js +5 -4
  15. package/dist/js/datepicker.min.js +4 -3
  16. package/dist/js/moo-ui.js +350 -15402
  17. package/dist/js/moo-ui.min.js +3 -23
  18. package/dist/js/sheet.js +46 -0
  19. package/dist/js/sidebar.js +3 -2
  20. package/dist/js/slider.js +12 -47
  21. package/dist/js/state.js +15 -41
  22. package/dist/js/table-of-contents.js +195 -0
  23. package/dist/release-manifest.json +4 -4
  24. package/package.json +5 -1
  25. package/scss/_components.scss +3 -2
  26. package/scss/_config.scss +15 -21
  27. package/scss/_settings.scss +0 -3
  28. package/scss/components/_accordion.scss +1 -12
  29. package/scss/components/_alert.scss +1 -12
  30. package/scss/components/_avatar.scss +1 -16
  31. package/scss/components/_badge.scss +1 -0
  32. package/scss/components/_breadcrumb.scss +1 -12
  33. package/scss/components/_button.scss +1 -29
  34. package/scss/components/_button_group.scss +1 -25
  35. package/scss/components/_card.scss +1 -29
  36. package/scss/components/_checkbox.scss +1 -38
  37. package/scss/components/_close_button.scss +1 -9
  38. package/scss/components/_collapsible.scss +1 -0
  39. package/scss/components/_combobox.scss +1 -6
  40. package/scss/components/_context-menu.scss +1 -11
  41. package/scss/components/_datatable.scss +46 -85
  42. package/scss/components/_datepicker.scss +1 -0
  43. package/scss/components/_dialog.scss +1 -14
  44. package/scss/components/_dropdown.scss +5 -51
  45. package/scss/components/_field.scss +1 -40
  46. package/scss/components/_input.scss +1 -5
  47. package/scss/components/_input_group.scss +1 -51
  48. package/scss/components/_kbd.scss +1 -0
  49. package/scss/components/_menubar.scss +1 -0
  50. package/scss/components/_navigation.scss +76 -6
  51. package/scss/components/_pagination.scss +1 -13
  52. package/scss/components/_popover.scss +1 -22
  53. package/scss/components/_progress.scss +1 -3
  54. package/scss/components/_radio_group.scss +105 -6
  55. package/scss/components/_search_trigger.scss +1 -2
  56. package/scss/components/_select.scss +1 -0
  57. package/scss/components/_separator.scss +1 -6
  58. package/scss/components/_sheet.scss +1 -17
  59. package/scss/components/_sidebar.scss +1 -2
  60. package/scss/components/_skeleton.scss +1 -8
  61. package/scss/components/_slider.scss +1 -2
  62. package/scss/components/_spinner.scss +1 -7
  63. package/scss/components/_switch.scss +1 -4
  64. package/scss/components/_table.scss +1 -2
  65. package/scss/components/_tabs.scss +1 -24
  66. package/scss/components/_textarea.scss +1 -0
  67. package/scss/components/_toast.scss +1 -4
  68. package/scss/components/_toc.scss +48 -0
  69. package/scss/components/_toggle_group.scss +1 -3
  70. package/scss/components/_tooltip.scss +1 -20
  71. package/scss/components/sidebar/_base.scss +0 -7
  72. package/scss/components/sidebar/_collapsed.scss +0 -6
  73. package/scss/components/sidebar/_identity.scss +0 -1
  74. package/scss/components/sidebar/_menus.scss +0 -17
  75. package/scss/foundations/_backdrop.scss +0 -26
  76. package/scss/foundations/_focus.scss +0 -56
  77. package/scss/foundations/_scope.scss +15 -0
  78. package/scss/layouts/_app.scss +0 -6
  79. package/scss/layouts/_page_grid.scss +0 -3
  80. package/scss/layouts/app/sidebar/_base.scss +0 -37
  81. package/scss/layouts/app/sidebar/_default.scss +0 -6
  82. package/scss/layouts/app/sidebar/_floating.scss +0 -11
  83. package/scss/layouts/app/sidebar/_inset.scss +0 -2
  84. package/scss/mixins/_banner.scss +9 -3
  85. package/scss/moo-core.scss +3 -6
  86. package/scss/moo-ui.scss +9 -5
  87. package/scss/settings/_bootstrap_overrides.scss +0 -8
  88. package/scss/settings/_component_variables.scss +4 -266
  89. package/scss/settings/_forms.scss +0 -37
  90. package/scss/settings/_palette.scss +0 -42
  91. package/scss/themes/_root.scss +3 -0
  92. package/scss/themes/_standalone.scss +0 -2
  93. package/scss/themes/_theme.scss +3 -2
  94. package/scss/utilities/_background_color.scss +18 -0
  95. package/scss/utilities/_border.scss +4 -0
  96. package/scss/utilities/_scroll_smooth.scss +9 -0
@@ -0,0 +1,46 @@
1
+ /*!
2
+ * Moo UI sheet.js v1.1.0-dev.1 (https://wpmoo.org/)
3
+ * Copyright 2026 WPMoo Authors
4
+ * Licensed under MIT (https://github.com/wpmoo-org/ui/blob/main/LICENSE)
5
+ */
6
+
7
+ // src/js/components/sheet.js
8
+ var states = /* @__PURE__ */ new WeakMap();
9
+ function initSheets(root = document) {
10
+ if (states.has(root)) {
11
+ return states.get(root);
12
+ }
13
+ const documentRoot = root.nodeType === 9 ? root : root.ownerDocument;
14
+ const Offcanvas = documentRoot?.defaultView?.bootstrap?.Offcanvas;
15
+ if (!Offcanvas) {
16
+ return () => {
17
+ };
18
+ }
19
+ const sheets = Array.from(root.querySelectorAll(".offcanvas.sheet"));
20
+ const focusInitialField = (event) => {
21
+ if (event.target === event.currentTarget) {
22
+ event.currentTarget.querySelector("[autofocus]:not(:disabled)")?.focus();
23
+ }
24
+ };
25
+ const dispose = () => {
26
+ if (states.get(root) !== dispose) {
27
+ return;
28
+ }
29
+ sheets.forEach((sheet) => {
30
+ sheet.removeEventListener("shown.bs.offcanvas", focusInitialField);
31
+ });
32
+ states.delete(root);
33
+ };
34
+ states.set(root, dispose);
35
+ sheets.forEach((sheet) => {
36
+ sheet.addEventListener("shown.bs.offcanvas", focusInitialField);
37
+ if (sheet.dataset.sheetOpenOnLoad === "true") {
38
+ sheet.removeAttribute("data-sheet-open-on-load");
39
+ Offcanvas.getOrCreateInstance(sheet).show();
40
+ }
41
+ });
42
+ return dispose;
43
+ }
44
+ export {
45
+ initSheets
46
+ };
@@ -1,8 +1,9 @@
1
1
  /*!
2
- * Moo UI sidebar.js v1.0.0-rc.9 (https://ui.wpmoo.org/)
3
- * Copyright 2026 WPMoo (https://wpmoo.org)
2
+ * Moo UI sidebar.js v1.1.0-dev.1 (https://wpmoo.org/)
3
+ * Copyright 2026 WPMoo Authors
4
4
  * Licensed under MIT (https://github.com/wpmoo-org/ui/blob/main/LICENSE)
5
5
  */
6
+
6
7
  var __defProp = Object.defineProperty;
7
8
  var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
8
9
 
package/dist/js/slider.js CHANGED
@@ -1,23 +1,20 @@
1
1
  /*!
2
- * Moo UI slider.js v1.0.0-rc.9 (https://ui.wpmoo.org/)
3
- * Copyright 2026 WPMoo (https://wpmoo.org)
2
+ * Moo UI slider.js v1.1.0-dev.1 (https://wpmoo.org/)
3
+ * Copyright 2026 WPMoo Authors
4
4
  * Licensed under MIT (https://github.com/wpmoo-org/ui/blob/main/LICENSE)
5
5
  */
6
- const instances = new WeakMap();
7
6
 
7
+ const instances = /* @__PURE__ */ new WeakMap();
8
8
  function asNumber(value, fallback) {
9
9
  const number = Number(value);
10
10
  return Number.isFinite(number) ? number : fallback;
11
11
  }
12
-
13
12
  function inputMinimum(input) {
14
13
  return asNumber(input.min, 0);
15
14
  }
16
-
17
15
  function inputMaximum(input) {
18
16
  return asNumber(input.max, 100);
19
17
  }
20
-
21
18
  function inputStep(input) {
22
19
  if (!input.step || input.step === "any") {
23
20
  return 1;
@@ -25,11 +22,9 @@ function inputStep(input) {
25
22
  const step = Number(input.step);
26
23
  return Number.isFinite(step) && step > 0 ? step : 1;
27
24
  }
28
-
29
25
  function clamp(value, min, max) {
30
26
  return Math.min(Math.max(value, min), max);
31
27
  }
32
-
33
28
  function decimalPlaces(value) {
34
29
  const text = String(value);
35
30
  if (!text.includes(".")) {
@@ -37,7 +32,6 @@ function decimalPlaces(value) {
37
32
  }
38
33
  return text.split(".").at(-1).length;
39
34
  }
40
-
41
35
  function snapToStep(value, input) {
42
36
  const min = inputMinimum(input);
43
37
  const max = inputMaximum(input);
@@ -49,29 +43,24 @@ function snapToStep(value, input) {
49
43
  const precision = Math.max(decimalPlaces(step), decimalPlaces(min));
50
44
  return clamp(Number(snapped.toFixed(precision)), min, max);
51
45
  }
52
-
53
46
  function valuePercent(input) {
54
47
  const min = inputMinimum(input);
55
48
  const max = inputMaximum(input);
56
49
  if (max <= min) {
57
50
  return 0;
58
51
  }
59
- return ((asNumber(input.value, min) - min) / (max - min)) * 100;
52
+ return (asNumber(input.value, min) - min) / (max - min) * 100;
60
53
  }
61
-
62
54
  function displayValue(input) {
63
55
  return input.value || String(inputMinimum(input));
64
56
  }
65
-
66
- export default class MooSlider {
57
+ class MooSlider {
67
58
  static getInstance(element) {
68
59
  return element?.nodeType === 1 ? instances.get(element) || null : null;
69
60
  }
70
-
71
61
  static getOrCreateInstance(element, config = {}) {
72
62
  return MooSlider.getInstance(element) || new MooSlider(element, config);
73
63
  }
74
-
75
64
  constructor(element, config = {}) {
76
65
  if (element?.nodeType !== 1 || !element.matches("[data-slider]")) {
77
66
  throw new TypeError("MooSlider requires a [data-slider] root element.");
@@ -80,7 +69,6 @@ export default class MooSlider {
80
69
  if (existing) {
81
70
  return existing;
82
71
  }
83
-
84
72
  this._element = element;
85
73
  this._document = element.ownerDocument;
86
74
  this._window = this._document.defaultView;
@@ -94,19 +82,16 @@ export default class MooSlider {
94
82
  );
95
83
  this._track = element.querySelector("[data-slider-track]");
96
84
  this._output = element.querySelector("[data-slider-output]");
97
-
98
85
  if (this._inputs.length < 1 || this._inputs.length > 2) {
99
86
  throw new TypeError("MooSlider requires one or two native range inputs.");
100
87
  }
101
88
  if (!this._track) {
102
89
  throw new TypeError("MooSlider requires a [data-slider-track] element.");
103
90
  }
104
-
105
91
  instances.set(element, this);
106
92
  this._bindEvents();
107
93
  this._sync();
108
94
  }
109
-
110
95
  dispose() {
111
96
  if (this._disposed) return;
112
97
  this._disposed = true;
@@ -120,7 +105,6 @@ export default class MooSlider {
120
105
  instances.delete(this._element);
121
106
  }
122
107
  }
123
-
124
108
  _listen(target, type, handler, options) {
125
109
  if (!target) {
126
110
  return;
@@ -128,7 +112,6 @@ export default class MooSlider {
128
112
  target.addEventListener(type, handler, options);
129
113
  this._listeners.push({ target, type, handler, options });
130
114
  }
131
-
132
115
  _listenDuringDrag(target, type, handler, options) {
133
116
  if (!target) {
134
117
  return;
@@ -136,7 +119,6 @@ export default class MooSlider {
136
119
  target.addEventListener(type, handler, options);
137
120
  this._dragListeners.push({ target, type, handler, options });
138
121
  }
139
-
140
122
  _clearDragListeners() {
141
123
  this._dragListeners.forEach(({ target, type, handler, options }) => {
142
124
  target.removeEventListener(type, handler, options);
@@ -145,7 +127,6 @@ export default class MooSlider {
145
127
  this._draggingInput = null;
146
128
  this._element.removeAttribute("data-slider-dragging");
147
129
  }
148
-
149
130
  _bindEvents() {
150
131
  this._inputs.forEach((input) => {
151
132
  this._listen(input, "input", () => this._sync(input));
@@ -157,9 +138,7 @@ export default class MooSlider {
157
138
  this._listen(input, "focusout", () => this._clearPointerFocus());
158
139
  });
159
140
  const forms = new Set(
160
- this._inputs
161
- .map((input) => input.form || input.closest?.("form"))
162
- .filter(Boolean),
141
+ this._inputs.map((input) => input.form || input.closest?.("form")).filter(Boolean)
163
142
  );
164
143
  forms.forEach((form) => {
165
144
  this._listen(form, "reset", () => {
@@ -168,19 +147,16 @@ export default class MooSlider {
168
147
  });
169
148
  this._listen(this._track, "pointerdown", (event) => this._handleTrackPointer(event));
170
149
  }
171
-
172
150
  _trigger(name) {
173
151
  this._element.dispatchEvent(
174
152
  new this._window.CustomEvent(`${name}.moo.slider`, {
175
- bubbles: true,
153
+ bubbles: true
176
154
  })
177
155
  );
178
156
  }
179
-
180
157
  _isRange() {
181
158
  return this._inputs.length === 2;
182
159
  }
183
-
184
160
  _sync(changedInput = null) {
185
161
  if (this._isRange()) {
186
162
  const [startInput, endInput] = this._inputs;
@@ -199,34 +175,29 @@ export default class MooSlider {
199
175
  this._syncAria(endInput);
200
176
  return;
201
177
  }
202
-
203
178
  const input = this._inputs[0];
204
179
  this._writeFill(0, valuePercent(input));
205
180
  this._writeOutput(displayValue(input));
206
181
  this._syncAria(input);
207
182
  }
208
-
209
183
  _syncAria(input) {
210
184
  input.setAttribute("aria-valuemin", String(inputMinimum(input)));
211
185
  input.setAttribute("aria-valuemax", String(inputMaximum(input)));
212
186
  input.setAttribute("aria-valuenow", displayValue(input));
213
187
  }
214
-
215
188
  _writeFill(start, end) {
216
189
  const clampedStart = clamp(start, 0, 100);
217
190
  const clampedEnd = clamp(end, 0, 100);
218
191
  this._element.style.setProperty("--moo-slider-start", `${Math.min(clampedStart, clampedEnd)}%`);
219
192
  this._element.style.setProperty("--moo-slider-end", `${Math.max(clampedStart, clampedEnd)}%`);
220
193
  }
221
-
222
194
  _writeOutput(value) {
223
195
  if (this._output) {
224
196
  this._output.textContent = value;
225
197
  }
226
198
  }
227
-
228
199
  _handleTrackPointer(event) {
229
- if (event.button !== undefined && event.button !== 0) {
200
+ if (event.button !== void 0 && event.button !== 0) {
230
201
  return;
231
202
  }
232
203
  const enabledInputs = this._inputs.filter((input) => !input.disabled);
@@ -240,23 +211,19 @@ export default class MooSlider {
240
211
  this._startDrag(target, event);
241
212
  this._setInputFromPointer(target, event);
242
213
  }
243
-
244
214
  _setPointerFocus() {
245
215
  this._element.setAttribute("data-slider-pointer-focus", "true");
246
216
  }
247
-
248
217
  _clearPointerFocus() {
249
218
  this._element.removeAttribute("data-slider-pointer-focus");
250
219
  }
251
-
252
220
  _startDrag(input, event) {
253
221
  this._clearDragListeners();
254
222
  this._draggingInput = input;
255
223
  this._element.setAttribute("data-slider-dragging", "true");
256
224
  this._track.setPointerCapture?.(event.pointerId);
257
-
258
225
  const pointerId = event.pointerId;
259
- const samePointer = (nextEvent) => pointerId === undefined || nextEvent.pointerId === pointerId;
226
+ const samePointer = (nextEvent) => pointerId === void 0 || nextEvent.pointerId === pointerId;
260
227
  const onMove = (moveEvent) => {
261
228
  if (!samePointer(moveEvent) || this._draggingInput !== input) {
262
229
  return;
@@ -274,12 +241,10 @@ export default class MooSlider {
274
241
  this._track.releasePointerCapture?.(pointerId);
275
242
  this._clearDragListeners();
276
243
  };
277
-
278
244
  this._listenDuringDrag(this._document, "pointermove", onMove);
279
245
  this._listenDuringDrag(this._document, "pointerup", onEnd);
280
246
  this._listenDuringDrag(this._document, "pointercancel", onEnd);
281
247
  }
282
-
283
248
  _setInputFromPointer(input, event, emitInput = true) {
284
249
  const percent = this._pointerPercent(event);
285
250
  const min = inputMinimum(input);
@@ -291,7 +256,6 @@ export default class MooSlider {
291
256
  input.dispatchEvent(new this._window.Event("input", { bubbles: true }));
292
257
  }
293
258
  }
294
-
295
259
  _pointerPercent(event) {
296
260
  const rect = this._track.getBoundingClientRect();
297
261
  const orientation = this._element.dataset.sliderOrientation || "horizontal";
@@ -300,13 +264,11 @@ export default class MooSlider {
300
264
  }
301
265
  return this._horizontalPointerPercent(event, rect);
302
266
  }
303
-
304
267
  _horizontalPointerPercent(event, rect) {
305
268
  const direction = this._window.getComputedStyle?.(this._element).direction;
306
269
  const offset = direction === "rtl" ? rect.right - event.clientX : event.clientX - rect.left;
307
270
  return clamp(offset / (rect.width || 1), 0, 1);
308
271
  }
309
-
310
272
  _nearestInput(percent, inputs) {
311
273
  if (inputs.length === 1) {
312
274
  return inputs[0];
@@ -322,3 +284,6 @@ export default class MooSlider {
322
284
  return endDistance < startDistance ? endInput : startInput;
323
285
  }
324
286
  }
287
+ export {
288
+ MooSlider as default
289
+ };
package/dist/js/state.js CHANGED
@@ -1,80 +1,56 @@
1
1
  /*!
2
- * Moo UI state bootstrap
3
- * Copyright 2026 WPMoo (https://wpmoo.org)
2
+ * Moo UI state.js v1.1.0-dev.1 (https://wpmoo.org/)
3
+ * Copyright 2026 WPMoo Authors
4
4
  * Licensed under MIT (https://github.com/wpmoo-org/ui/blob/main/LICENSE)
5
5
  */
6
+
6
7
  (() => {
7
8
  const ownerDocument = typeof document === "undefined" ? null : document;
8
9
  const owner = ownerDocument?.currentScript?.parentElement;
9
10
  if (!owner) return;
10
-
11
- const view =
12
- ownerDocument?.defaultView ||
13
- (typeof window === "undefined" ? null : window);
14
-
11
+ const view = ownerDocument?.defaultView || (typeof window === "undefined" ? null : window);
15
12
  if (owner.matches?.('.moo-ui[data-bs-theme="light"], .moo-ui[data-bs-theme="dark"]')) {
16
13
  const body = ownerDocument?.body;
17
- const documentOwner =
18
- owner.dataset?.mooDocumentOwner === "true" &&
19
- owner.parentElement === body &&
20
- owner === body?.firstElementChild;
21
- const normalizeDirection = (value) =>
22
- value === "ltr" || value === "rtl" ? value : null;
14
+ const documentOwner = owner.dataset?.mooDocumentOwner === "true" && owner.parentElement === body && owner === body?.firstElementChild;
15
+ const normalizeDirection = (value) => value === "ltr" || value === "rtl" ? value : null;
23
16
  const ownerDirection = normalizeDirection(
24
- owner.getAttribute?.("dir") || owner.dir,
17
+ owner.getAttribute?.("dir") || owner.dir
25
18
  );
26
19
  const documentDirection = normalizeDirection(
27
- ownerDocument?.documentElement?.getAttribute?.("dir") ||
28
- ownerDocument?.documentElement?.dir,
20
+ ownerDocument?.documentElement?.getAttribute?.("dir") || ownerDocument?.documentElement?.dir
29
21
  );
30
22
  owner.__mooStateBaseline = {
31
23
  theme: owner.dataset.bsTheme === "dark" ? "dark" : "light",
32
- direction: documentOwner ? documentDirection || "ltr" : ownerDirection,
24
+ direction: documentOwner ? documentDirection || "ltr" : ownerDirection
33
25
  };
34
-
35
26
  const readPreference = (axis, allowed) => {
36
27
  const dataKey = axis === "theme" ? "mooThemeKey" : "mooDirectionKey";
37
28
  const explicit = owner.dataset?.[dataKey]?.trim();
38
- const key = explicit || (documentOwner ? `moo:${axis}` : null);
39
- if (!key) return null;
29
+ const key2 = explicit || (documentOwner ? `moo:${axis}` : null);
30
+ if (!key2) return null;
40
31
  try {
41
- const value = view?.localStorage?.getItem(key);
32
+ const value = view?.localStorage?.getItem(key2);
42
33
  return allowed.includes(value) ? value : null;
43
34
  } catch (_) {
44
35
  return null;
45
36
  }
46
37
  };
47
-
48
38
  const systemPrefersDark = () => {
49
39
  try {
50
40
  const matchMedia = view?.matchMedia;
51
41
  if (typeof matchMedia !== "function") return null;
52
42
  return Boolean(
53
- matchMedia.call(view, "(prefers-color-scheme: dark)")?.matches,
43
+ matchMedia.call(view, "(prefers-color-scheme: dark)")?.matches
54
44
  );
55
45
  } catch (_) {
56
46
  return null;
57
47
  }
58
48
  };
59
-
60
49
  try {
61
50
  const storedTheme = readPreference("theme", ["light", "dark", "system"]);
62
- const mediaPrefersDark =
63
- storedTheme === "system" ? systemPrefersDark() : null;
64
- const resolvedTheme =
65
- storedTheme === "dark"
66
- ? "dark"
67
- : storedTheme === "light"
68
- ? "light"
69
- : storedTheme === "system" && mediaPrefersDark !== null
70
- ? mediaPrefersDark
71
- ? "dark"
72
- : "light"
73
- : owner.dataset.bsTheme === "dark"
74
- ? "dark"
75
- : "light";
51
+ const mediaPrefersDark = storedTheme === "system" ? systemPrefersDark() : null;
52
+ const resolvedTheme = storedTheme === "dark" ? "dark" : storedTheme === "light" ? "light" : storedTheme === "system" && mediaPrefersDark !== null ? mediaPrefersDark ? "dark" : "light" : owner.dataset.bsTheme === "dark" ? "dark" : "light";
76
53
  owner.dataset.bsTheme = resolvedTheme;
77
-
78
54
  const storedDirection = readPreference("direction", ["ltr", "rtl"]);
79
55
  if (storedDirection) {
80
56
  if (documentOwner) ownerDocument.documentElement.dir = storedDirection;
@@ -85,7 +61,6 @@
85
61
  }
86
62
  return;
87
63
  }
88
-
89
64
  if (!owner.matches?.('[data-slot="sidebar-wrapper"][data-sidebar-key]')) return;
90
65
  const key = owner.dataset?.sidebarKey;
91
66
  if (!key) return;
@@ -95,7 +70,6 @@
95
70
  owner.dataset.sidebarState = state;
96
71
  }
97
72
  } catch (_) {
98
- // Restricted browsing contexts may deny localStorage.
99
73
  } finally {
100
74
  owner.dataset.sidebarStateReady = "";
101
75
  }
@@ -0,0 +1,195 @@
1
+ /*!
2
+ * Moo UI table-of-contents.js v1.1.0-dev.1 (https://wpmoo.org/)
3
+ * Copyright 2026 WPMoo Authors
4
+ * Licensed under MIT (https://github.com/wpmoo-org/ui/blob/main/LICENSE)
5
+ */
6
+
7
+ const instances = /* @__PURE__ */ new WeakMap();
8
+ const ownerSelector = '.moo-ui[data-bs-theme="light"], .moo-ui[data-bs-theme="dark"]';
9
+ function activationOffset(options, previous = 0) {
10
+ if (!options || typeof options !== "object" || Array.isArray(options)) {
11
+ throw new TypeError("TableOfContents options must be an object.");
12
+ }
13
+ const value = options.activationOffset === void 0 ? previous : options.activationOffset;
14
+ if (typeof value !== "number" || !Number.isFinite(value) || value < 0) {
15
+ throw new TypeError("TableOfContents activationOffset must be a finite nonnegative number.");
16
+ }
17
+ return value;
18
+ }
19
+ function fragmentId(href) {
20
+ if (!href?.startsWith("#") || href.length === 1) return null;
21
+ try {
22
+ return decodeURIComponent(href.slice(1));
23
+ } catch {
24
+ return null;
25
+ }
26
+ }
27
+ class TableOfContents {
28
+ static getInstance(element) {
29
+ return element?.nodeType === 1 ? instances.get(element) || null : null;
30
+ }
31
+ static getOrCreateInstance(element, options = {}) {
32
+ return TableOfContents.getInstance(element) || new TableOfContents(element, options);
33
+ }
34
+ constructor(element, options = {}) {
35
+ if (element?.nodeType !== 1 || !element.matches("[data-toc]")) {
36
+ throw new TypeError("TableOfContents requires a [data-toc] root element.");
37
+ }
38
+ const existing = instances.get(element);
39
+ if (existing) return existing;
40
+ this._element = element;
41
+ this._document = element.ownerDocument;
42
+ this._window = this._document.defaultView;
43
+ this._offset = activationOffset(options);
44
+ this._listeners = [];
45
+ this._originalLinks = /* @__PURE__ */ new Map();
46
+ this._originalStatus = /* @__PURE__ */ new Map();
47
+ this._originalMarker = {
48
+ attribute: element.getAttribute("data-toc-marker"),
49
+ properties: ["--moo-toc-marker-offset", "--moo-toc-marker-height"].map((name) => ({
50
+ name,
51
+ value: element.style.getPropertyValue(name),
52
+ priority: element.style.getPropertyPriority(name)
53
+ }))
54
+ };
55
+ this._links = [];
56
+ this._targets = [];
57
+ this._frame = 0;
58
+ this._disposed = false;
59
+ instances.set(element, this);
60
+ this.refresh();
61
+ }
62
+ refresh(options = {}) {
63
+ if (this._disposed) return this;
64
+ const offset = activationOffset(options, this._offset);
65
+ this._clearListeners();
66
+ this._offset = offset;
67
+ const owner = this._element.closest(ownerSelector);
68
+ const contentId = this._element.getAttribute("data-toc-content");
69
+ const scope = contentId ? this._document.getElementById(contentId) : owner;
70
+ const rootId = this._element.getAttribute("data-toc-scroll-root");
71
+ this._scrollElement = rootId ? this._document.getElementById(rootId) : null;
72
+ this._status = Array.from(this._element.querySelectorAll("[data-toc-current]")).find((node) => node.closest("[data-toc]") === this._element) || null;
73
+ if (this._status && !this._originalStatus.has(this._status)) {
74
+ this._originalStatus.set(this._status, this._status.textContent);
75
+ }
76
+ this._overview = this._status ? this._originalStatus.get(this._status) : "";
77
+ this._links = Array.from(this._element.querySelectorAll('a[href^="#"]')).filter((link) => link.closest("[data-toc]") === this._element).map((link) => ({ link, id: fragmentId(link.getAttribute("href")) }));
78
+ this._links.forEach(({ link }) => {
79
+ if (!this._originalLinks.has(link)) {
80
+ this._originalLinks.set(link, {
81
+ active: link.classList.contains("active"),
82
+ current: link.getAttribute("aria-current")
83
+ });
84
+ }
85
+ });
86
+ this._targets = [];
87
+ this._activate(null);
88
+ if (!owner || !scope || scope.closest(ownerSelector) !== owner || rootId && (!this._scrollElement || !this._scrollElement.contains(scope))) {
89
+ return this;
90
+ }
91
+ const seen = /* @__PURE__ */ new Set();
92
+ this._links.forEach(({ id, link }) => {
93
+ const target = id ? this._document.getElementById(id) : null;
94
+ if (!target || seen.has(id) || !scope.contains(target) || target.closest(ownerSelector) !== owner) return;
95
+ seen.add(id);
96
+ this._targets.push({ id, target, label: link.textContent.trim() });
97
+ });
98
+ if (!this._targets.length) return this;
99
+ const queue = () => this._requestUpdate();
100
+ this._listen(this._scrollElement || this._window, "scroll", queue, { passive: true });
101
+ this._listen(this._window, "resize", queue);
102
+ this._listen(this._window, "hashchange", queue);
103
+ this._listen(this._element, "click", queue);
104
+ this._update();
105
+ return this;
106
+ }
107
+ dispose() {
108
+ if (this._disposed) return;
109
+ this._disposed = true;
110
+ this._clearListeners();
111
+ this._originalLinks.forEach(({ active, current }, link) => {
112
+ link.classList.toggle("active", active);
113
+ if (current === null) link.removeAttribute("aria-current");
114
+ else link.setAttribute("aria-current", current);
115
+ });
116
+ this._originalStatus.forEach((text, node) => {
117
+ node.textContent = text;
118
+ });
119
+ const { attribute, properties } = this._originalMarker;
120
+ if (attribute === null) this._element.removeAttribute("data-toc-marker");
121
+ else this._element.setAttribute("data-toc-marker", attribute);
122
+ properties.forEach(({ name, value, priority }) => {
123
+ if (value) this._element.style.setProperty(name, value, priority);
124
+ else this._element.style.removeProperty(name);
125
+ });
126
+ this._originalLinks.clear();
127
+ this._originalStatus.clear();
128
+ this._targets = [];
129
+ this._links = [];
130
+ if (instances.get(this._element) === this) instances.delete(this._element);
131
+ }
132
+ _listen(target, type, handler, options) {
133
+ target.addEventListener(type, handler, options);
134
+ this._listeners.push({ target, type, handler, options });
135
+ }
136
+ _clearListeners() {
137
+ this._listeners.forEach(({ target, type, handler, options }) => target.removeEventListener(type, handler, options));
138
+ this._listeners = [];
139
+ if (this._frame) this._window.cancelAnimationFrame(this._frame);
140
+ this._frame = 0;
141
+ }
142
+ _requestUpdate() {
143
+ if (this._disposed || this._frame) return;
144
+ this._frame = this._window.requestAnimationFrame(() => {
145
+ this._frame = 0;
146
+ this._update();
147
+ });
148
+ }
149
+ _update() {
150
+ if (this._disposed) return;
151
+ const targets = this._targets.filter(({ target }) => target.isConnected && target.getClientRects().length > 0);
152
+ const scroller = this._scrollElement || this._document.scrollingElement;
153
+ const line = (this._scrollElement ? this._scrollElement.getBoundingClientRect().top + this._scrollElement.clientTop : 0) + this._offset;
154
+ let active = null;
155
+ targets.forEach((entry) => {
156
+ if (entry.target.getBoundingClientRect().top <= line + 1) active = entry;
157
+ });
158
+ const canScroll = scroller && scroller.scrollHeight > scroller.clientHeight + 1;
159
+ if (!canScroll) {
160
+ const hashId = fragmentId(this._window.location.hash);
161
+ active = targets.find(({ id }) => id === hashId) || active;
162
+ }
163
+ if (targets.length && canScroll && scroller.scrollTop + scroller.clientHeight >= scroller.scrollHeight - 1) {
164
+ active = targets[targets.length - 1];
165
+ }
166
+ this._activate(active);
167
+ }
168
+ _activate(active) {
169
+ let activeLink = null;
170
+ this._links.forEach(({ link, id }) => {
171
+ const current = active !== null && id === active.id;
172
+ link.classList.toggle("active", current);
173
+ if (current) {
174
+ link.setAttribute("aria-current", "location");
175
+ activeLink || (activeLink = link);
176
+ } else link.removeAttribute("aria-current");
177
+ });
178
+ if (this._status) this._status.textContent = active ? active.label : this._overview;
179
+ this._updateMarker(activeLink);
180
+ }
181
+ _updateMarker(link) {
182
+ const list = this._element.querySelector(":scope > .nav");
183
+ const text = link?.querySelector("span");
184
+ if (!list?.contains(link) || !text?.offsetHeight || !this._element.getClientRects().length) {
185
+ this._element.removeAttribute("data-toc-marker");
186
+ return;
187
+ }
188
+ this._element.style.setProperty("--moo-toc-marker-offset", `${link.offsetTop + text.offsetTop}px`);
189
+ this._element.style.setProperty("--moo-toc-marker-height", `${text.offsetHeight}px`);
190
+ this._element.setAttribute("data-toc-marker", "");
191
+ }
192
+ }
193
+ export {
194
+ TableOfContents as default
195
+ };
@@ -3,22 +3,22 @@
3
3
  {
4
4
  "export": "./moo.css",
5
5
  "path": "dist/assets/css/moo.css",
6
- "sha256": "9f5228c6387b728efe2f341aea9ff09c193f621e778ac413fee9f1cd84992791"
6
+ "sha256": "38bb85c89e2f1f2f043119a9290cb58dd74a1572090b5fc41930c7c57a90c894"
7
7
  },
8
8
  {
9
9
  "export": "./moo-ui.css",
10
10
  "path": "dist/assets/css/moo-ui.css",
11
- "sha256": "ca54a5ff1e8a1fa2ac704e3b55e63aa4517353aede35c0bf509c485e6d04a2c6"
11
+ "sha256": "d6947a3a27a4315591cf9f6b92e1fd1d958a5fceb1523b709487d54fcbefc815"
12
12
  },
13
13
  {
14
14
  "export": "./state.js",
15
15
  "path": "dist/js/state.js",
16
- "sha256": "094cc7bdce8934b846d77476727fd7627062538612f7fcfa8fbd873e44c5e264"
16
+ "sha256": "3560c86c8623a3d523ec763456d984e009d8a7fa5834de168b153ba9d242f1c2"
17
17
  }
18
18
  ],
19
19
  "package": {
20
20
  "name": "@wpmoo/ui",
21
- "version": "1.0.0-rc.9"
21
+ "version": "1.1.0-dev.1"
22
22
  },
23
23
  "schemaVersion": 1
24
24
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wpmoo/ui",
3
- "version": "1.0.0-rc.9",
3
+ "version": "1.1.0-dev.1",
4
4
  "description": "Bootstrap markup. shadcn feel. A frontend-framework-free component system for server-rendered apps.",
5
5
  "license": "MIT",
6
6
  "author": "WPMoo (https://wpmoo.org)",
@@ -38,6 +38,8 @@
38
38
  "./context-menu.js": "./dist/js/context-menu.js",
39
39
  "./datatable.js": "./dist/js/datatable.js",
40
40
  "./slider.js": "./dist/js/slider.js",
41
+ "./table-of-contents.js": "./dist/js/table-of-contents.js",
42
+ "./sheet.js": "./dist/js/sheet.js",
41
43
  "./chart.js": "./dist/js/chart.js",
42
44
  "./chart.min.js": "./dist/js/chart.min.js",
43
45
  "./datepicker.js": "./dist/js/datepicker.js",
@@ -64,6 +66,8 @@
64
66
  "dist/js/context-menu.js",
65
67
  "dist/js/datatable.js",
66
68
  "dist/js/slider.js",
69
+ "dist/js/table-of-contents.js",
70
+ "dist/js/sheet.js",
67
71
  "dist/js/chart.js",
68
72
  "dist/js/chart.min.js",
69
73
  "dist/js/datepicker.js",