@oscarpalmer/oui 0.28.0 → 0.29.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 (46) hide show
  1. package/dist/js/dialog.d.mts +1 -1
  2. package/dist/js/dialog.mjs +19 -20
  3. package/dist/js/{draggable-zszaW1qS.d.mts → draggable-fIYXd8eO.d.mts} +3 -3
  4. package/dist/js/floatable/floatable.embedded.d.mts +2 -2
  5. package/dist/js/floatable/floatable.embedded.mjs +17 -16
  6. package/dist/js/floatable/floatable.standalone.d.mts +1 -1
  7. package/dist/js/{floatable.embedded-YxGwzgbE.d.mts → floatable.embedded-DUR8bQBK.d.mts} +1 -2
  8. package/dist/js/focus-trap/focus-trap.embedded.mjs +6 -5
  9. package/dist/js/index.d.mts +2 -2
  10. package/dist/js/index.mjs +1 -0
  11. package/dist/js/internal/attributable.mjs +3 -4
  12. package/dist/js/internal/draggable.d.mts +1 -1
  13. package/dist/js/internal/draggable.mjs +11 -8
  14. package/dist/js/internal/navigable.d.mts +32 -0
  15. package/dist/js/internal/navigable.mjs +107 -0
  16. package/dist/js/menu.d.mts +1 -0
  17. package/dist/js/menu.mjs +168 -0
  18. package/dist/js/movable/movable.embedded.d.mts +1 -1
  19. package/dist/js/movable/movable.embedded.mjs +8 -13
  20. package/dist/js/movable/movable.standalone.d.mts +1 -1
  21. package/dist/js/popover.d.mts +1 -1
  22. package/dist/js/popover.mjs +28 -13
  23. package/dist/js/resizable/resizable.embedded.mjs +16 -11
  24. package/dist/js/sortable/sortable.embedded.d.mts +1 -1
  25. package/dist/js/sortable/sortable.embedded.mjs +15 -13
  26. package/dist/js/sortable/sortable.standalone.d.mts +1 -1
  27. package/dist/js/splitter.mjs +29 -25
  28. package/dist/js/toolbar.mjs +12 -88
  29. package/dist/js/tooltip.d.mts +1 -1
  30. package/dist/js/tooltip.mjs +12 -17
  31. package/package.json +7 -3
  32. package/src/js/dialog.ts +22 -26
  33. package/src/js/floatable/floatable.embedded.ts +19 -18
  34. package/src/js/focus-trap/focus-trap.embedded.ts +6 -5
  35. package/src/js/index.ts +1 -0
  36. package/src/js/internal/attributable.ts +3 -5
  37. package/src/js/internal/draggable.ts +12 -9
  38. package/src/js/internal/navigable.ts +247 -0
  39. package/src/js/menu.ts +345 -0
  40. package/src/js/movable/movable.embedded.ts +10 -13
  41. package/src/js/popover.ts +42 -16
  42. package/src/js/resizable/resizable.embedded.ts +17 -10
  43. package/src/js/sortable/sortable.embedded.ts +17 -22
  44. package/src/js/splitter.ts +40 -35
  45. package/src/js/toolbar.ts +27 -178
  46. package/src/js/tooltip.ts +18 -30
@@ -1,105 +1,29 @@
1
1
  import { attributable } from "./internal/attributable.mjs";
2
- import { on } from "@oscarpalmer/toretto/event";
3
- import { isHTMLOrSVGElement } from "@oscarpalmer/toretto/is";
4
- import { clamp } from "@oscarpalmer/atoms/number";
5
- import { findAncestor } from "@oscarpalmer/toretto/find";
2
+ import { getNavigable, updateNavigableFocus } from "./internal/navigable.mjs";
6
3
  //#region src/js/toolbar.ts
7
4
  var OuiToolbar = class {
8
- #state;
9
- constructor(state) {
10
- this.#state = state;
11
- updateFocus(this.#state, false);
5
+ #navigable;
6
+ constructor(navigable) {
7
+ this.#navigable = navigable;
8
+ updateNavigableFocus(this.#navigable, false);
12
9
  }
13
10
  destroy() {
14
- for (const listener of this.#state.listeners) listener();
15
- this.#state.active = void 0;
16
- this.#state.element = void 0;
17
- this.#state.listeners = void 0;
11
+ this.#navigable.destroy();
12
+ this.#navigable = void 0;
18
13
  }
19
14
  };
20
15
  function addToolbar(element) {
21
- if (!instances.has(element)) instances.set(element, new OuiToolbar(getState(element)));
22
- }
23
- function getItems(element) {
24
- return [...element.querySelectorAll(SELECTOR)].filter(isValidItem);
25
- }
26
- function getOrientation(element) {
27
- const attribute = element.getAttribute(ARIA_ORIENTATION);
28
- return ORIENTATIONS.has(attribute) ? attribute : ORIENTATION_HORIZONTAL;
29
- }
30
- function getState(element) {
31
- const state = {
32
- element,
33
- active: { index: 0 },
34
- listeners: [on(element, EVENT_FOCUSIN, (event) => {
35
- onFocusin(state, event);
36
- }), on(element, EVENT_KEYDOWN, (event) => {
37
- onKeydown(state, event);
38
- }, { passive: false })],
39
- orientation: getOrientation(element)
40
- };
41
- return state;
42
- }
43
- function isValidItem(element) {
44
- return PROPERTY_DISABLED in element ? element.disabled !== true : true;
45
- }
46
- function onKeydown(state, event) {
47
- if (!KEYS_ALL.has(event.key)) return;
48
- const items = getItems(state.element);
49
- if (items.length === 0) return;
50
- let index;
51
- if (KEYS_ABSOLUTE.has(event.key)) index = event.key === KEY_HOME ? 0 : items.length - 1;
52
- else if (state.orientation === ORIENTATION_HORIZONTAL ? KEYS_HORIZONTAL.has(event.key) : KEYS_VERTICAL.has(event.key)) {
53
- if (state.active.element instanceof HTMLSelectElement) event.preventDefault();
54
- index = clamp(state.active.index + (KEYS_NEXT.has(event.key) ? 1 : -1), 0, items.length - 1, true);
55
- }
56
- if (index == null || index === state.active.index) return;
57
- state.active.element = items[index];
58
- state.active.index = index;
59
- updateFocus(state, true, items);
60
- }
61
- function onFocusin(state, event) {
62
- const item = findAncestor(event, SELECTOR);
63
- if (!isHTMLOrSVGElement(item) || !isValidItem(item)) return;
64
- const items = getItems(state.element);
65
- const index = items.indexOf(item);
66
- if (index === -1) return;
67
- state.active.element = item;
68
- state.active.index = index;
69
- updateFocus(state, false, items);
16
+ if (!instances.has(element)) instances.set(element, new OuiToolbar(getNavigable(element, {
17
+ role: "toolbar",
18
+ selector: SELECTOR,
19
+ vertical: false
20
+ })));
70
21
  }
71
22
  function removeToolbar(element) {
72
23
  instances.get(element)?.destroy();
73
24
  instances.delete(element);
74
25
  }
75
- function updateFocus(state, focus, items) {
76
- const updated = items ?? getItems(state.element);
77
- const { length } = updated;
78
- if (length === 0) return;
79
- for (let index = 0; index < length; index += 1) updated[index].tabIndex = index === state.active.index ? 0 : -1;
80
- state.active.element ??= updated[state.active.index];
81
- if (focus) state.active.element?.focus();
82
- }
83
- const ARIA_ORIENTATION = "aria-orientation";
84
26
  const ATTRIBUTE = "oui-toolbar";
85
- const EVENT_KEYDOWN = "keydown";
86
- const EVENT_FOCUSIN = "focusin";
87
- const KEY_ARROW_DOWN = "ArrowDown";
88
- const KEY_ARROW_RIGHT = "ArrowRight";
89
- const KEY_END = "End";
90
- const KEY_HOME = "Home";
91
- const KEYS_ABSOLUTE = /* @__PURE__ */ new Set([KEY_END, KEY_HOME]);
92
- const KEYS_HORIZONTAL = /* @__PURE__ */ new Set([KEY_ARROW_RIGHT, "ArrowLeft"]);
93
- const KEYS_VERTICAL = /* @__PURE__ */ new Set([KEY_ARROW_DOWN, "ArrowUp"]);
94
- const KEYS_ALL = /* @__PURE__ */ new Set([
95
- ...KEYS_ABSOLUTE,
96
- ...KEYS_HORIZONTAL,
97
- ...KEYS_VERTICAL
98
- ]);
99
- const KEYS_NEXT = /* @__PURE__ */ new Set([KEY_ARROW_DOWN, KEY_ARROW_RIGHT]);
100
- const ORIENTATION_HORIZONTAL = "horizontal";
101
- const ORIENTATIONS = /* @__PURE__ */ new Set([ORIENTATION_HORIZONTAL, "vertical"]);
102
- const PROPERTY_DISABLED = "disabled";
103
27
  const SELECTOR = `[${ATTRIBUTE}-item]`;
104
28
  const instances = /* @__PURE__ */ new WeakMap();
105
29
  attributable(ATTRIBUTE, addToolbar, removeToolbar);
@@ -1,4 +1,4 @@
1
- import { i as OuiFloatable } from "./floatable.embedded-YxGwzgbE.mjs";
1
+ import { r as OuiFloatable } from "./floatable.embedded-DUR8bQBK.mjs";
2
2
 
3
3
  //#region src/js/tooltip.d.ts
4
4
  declare class OuiTooltip {
@@ -1,8 +1,9 @@
1
1
  import { createEmbeddedFloatable } from "./floatable/floatable.embedded.mjs";
2
2
  import { attributable } from "./internal/attributable.mjs";
3
+ import { getAria } from "@oscarpalmer/toretto/aria";
3
4
  import { on } from "@oscarpalmer/toretto/event";
4
5
  import { isNullableOrWhitespace } from "@oscarpalmer/atoms/is";
5
- import { isHTMLOrSVGElement } from "@oscarpalmer/toretto/is";
6
+ import { setAttribute } from "@oscarpalmer/toretto/attribute";
6
7
  import { findAncestor } from "@oscarpalmer/toretto/find";
7
8
  import { createElement } from "@oscarpalmer/toretto/create";
8
9
  //#region src/js/tooltip.ts
@@ -87,12 +88,13 @@ function destroyTooltip(state) {
87
88
  state.tooltip = void 0;
88
89
  }
89
90
  function findElements(attribute) {
91
+ if (attribute == null) return;
90
92
  const ids = attribute?.split(EXPRESSION_WHITESPACE) ?? [];
91
93
  const elements = [];
92
94
  for (const id of ids) {
93
95
  if (isNullableOrWhitespace(id)) continue;
94
96
  const element = document.querySelector(`#${id}`);
95
- if (isHTMLOrSVGElement(element)) {
97
+ if (element != null) {
96
98
  const cloned = element.cloneNode(true);
97
99
  cloned.hidden = false;
98
100
  elements.push(cloned);
@@ -101,21 +103,20 @@ function findElements(attribute) {
101
103
  return elements;
102
104
  }
103
105
  function getContent() {
104
- const content = createElement(CONTENT_TAGNAME, {
106
+ const content = createElement("div", {
105
107
  popover: "hint",
106
108
  role: "tooltip",
107
109
  tabIndex: -1
108
110
  });
109
- content.setAttribute(ATTRIBUTE_CONTENT, "");
111
+ setAttribute(content, ATTRIBUTE_CONTENT, "");
110
112
  return content;
111
113
  }
112
114
  function getElements(anchor) {
113
- let elements = findElements(anchor.getAttribute(ARIA_LABELLEDBY));
114
- if (elements.length > 0) return elements;
115
- elements = findElements(anchor.getAttribute(ARIA_DESCRIBEDBY));
116
- if (elements.length > 0) return elements;
117
- let text = anchor.getAttribute(ARIA_LABEL);
118
- if (isNullableOrWhitespace(text)) text = anchor.getAttribute(ARIA_DESCRIPTION);
115
+ let elements = findElements(getAria(anchor, "labelledby"));
116
+ if (elements == null || elements.length === 0) elements = findElements(getAria(anchor, "describedby"));
117
+ if (elements != null && elements.length > 0) return elements;
118
+ let text = getAria(anchor, "label");
119
+ if (isNullableOrWhitespace(text)) text = getAria(anchor, "description");
119
120
  if (isNullableOrWhitespace(text)) return;
120
121
  const paragraph = document.createElement("p");
121
122
  paragraph.textContent = text;
@@ -157,8 +158,7 @@ function onPointermove(event) {
157
158
  activate(event, false);
158
159
  }
159
160
  function onToggle(event) {
160
- if (event.newState === "open") content.setAttribute(ATTRIBUTE_CONTENT_OPEN, "");
161
- else content.removeAttribute(ATTRIBUTE_CONTENT_OPEN);
161
+ setAttribute(content, ATTRIBUTE_CONTENT_OPEN, event.newState === "open" ? "" : void 0);
162
162
  }
163
163
  function removeTooltip(element) {
164
164
  const state = states.get(element);
@@ -175,11 +175,6 @@ function removeTooltip(element) {
175
175
  function setTooltipDelay(value) {
176
176
  if (typeof value === "number" && !Number.isNaN(value) && value >= 0) delay = value;
177
177
  }
178
- const ARIA_DESCRIBEDBY = "aria-describedby";
179
- const ARIA_DESCRIPTION = "aria-description";
180
- const ARIA_LABEL = "aria-label";
181
- const ARIA_LABELLEDBY = "aria-labelledby";
182
- const CONTENT_TAGNAME = "div";
183
178
  const EXPRESSION_WHITESPACE = /\s+/;
184
179
  const ATTRIBUTE = "oui-tooltip";
185
180
  const ATTRIBUTE_CONTENT = `${ATTRIBUTE}-content`;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oscarpalmer/oui",
3
- "version": "0.28.0",
3
+ "version": "0.29.1",
4
4
  "description": "Styles, components, and utilities for building websites and applications.",
5
5
  "keywords": [
6
6
  "css",
@@ -49,6 +49,10 @@
49
49
  "types": "./dist/js/movable/movable.standalone.d.mts",
50
50
  "default": "./dist/js/movable/movable.standalone.mjs"
51
51
  },
52
+ "./menu": {
53
+ "types": "./dist/js/menu.d.mts",
54
+ "default": "./dist/js/menu.mjs"
55
+ },
52
56
  "./popover": {
53
57
  "types": "./dist/js/popover.d.mts",
54
58
  "default": "./dist/js/popover.mjs"
@@ -87,10 +91,10 @@
87
91
  },
88
92
  "dependencies": {
89
93
  "@oscarpalmer/atoms": "^0.188.1",
90
- "@oscarpalmer/toretto": "^0.46"
94
+ "@oscarpalmer/toretto": "^0.47.3"
91
95
  },
92
96
  "devDependencies": {
93
- "@oxlint/plugins": "^1.72",
97
+ "@oxlint/plugins": "^1.73",
94
98
  "@vitest/coverage-istanbul": "^4.1",
95
99
  "autoprefixer": "^10.5",
96
100
  "concurrently": "^10",
package/src/js/dialog.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import {getAria, setAria} from '@oscarpalmer/toretto/aria';
1
2
  import {on} from '@oscarpalmer/toretto/event';
2
3
  import type {RemovableEventListener} from '@oscarpalmer/toretto/models';
3
4
  import {getOnBeforeToggleListener} from './floatable/floatable.embedded';
@@ -6,6 +7,8 @@ import {ATTRIBUTE_MOVABLE_HANDLE} from './movable/movable.embedded';
6
7
  import {createMovable, type OuiMovable} from './movable/movable.standalone';
7
8
  import {createResizable, RESIZABLE_ATTRIBUTE_HANDLE} from './resizable/resizable.embedded';
8
9
  import type {OuiResizable} from './resizable/resizable.standalone';
10
+ import {getAttribute, setAttribute} from '@oscarpalmer/toretto/attribute';
11
+ import {setStyles} from '@oscarpalmer/toretto/style';
9
12
 
10
13
  // #region Types
11
14
 
@@ -68,7 +71,7 @@ class OuiDialog {
68
71
 
69
72
  const isModal = modal !== false;
70
73
 
71
- element.setAttribute(ARIA_MODAL, String(isModal));
74
+ setAria(element, 'modal', isModal);
72
75
 
73
76
  if (isModal) {
74
77
  element.showModal();
@@ -104,7 +107,8 @@ function addDialog(element: HTMLElement): void {
104
107
  function destroyDialog(state: OuiDialogState): void {
105
108
  state.element.close();
106
109
 
107
- state.element.setAttribute(ARIA_MODAL, String(state.modal));
110
+ setAria(state.element, 'modal', state.modal);
111
+
108
112
  state.element.removeAttribute(ATTRIBUTE_OPEN);
109
113
 
110
114
  for (const listener of state.listeners) {
@@ -139,11 +143,11 @@ function getMovable(element: HTMLDialogElement): OuiMovable | undefined {
139
143
  }
140
144
 
141
145
  for (const handle of handles) {
142
- handle.setAttribute(ATTRIBUTE_MOVABLE_HANDLE, '');
146
+ setAttribute(handle, ATTRIBUTE_MOVABLE_HANDLE, '');
143
147
  }
144
148
 
145
149
  return createMovable(element, {
146
- container: element.getAttribute(ATTRIBUTE_CONTAINER) ?? undefined,
150
+ container: getAttribute(element, ATTRIBUTE_CONTAINER),
147
151
  });
148
152
  }
149
153
 
@@ -154,7 +158,7 @@ function getResizable(element: HTMLDialogElement): OuiResizable | undefined {
154
158
  return;
155
159
  }
156
160
 
157
- handle.setAttribute(RESIZABLE_ATTRIBUTE_HANDLE, '');
161
+ setAttribute(handle, RESIZABLE_ATTRIBUTE_HANDLE, '');
158
162
 
159
163
  return createResizable(element);
160
164
  }
@@ -165,16 +169,16 @@ function getState(element: HTMLDialogElement): OuiDialogState {
165
169
  dialog: undefined as never,
166
170
  listeners: [
167
171
  getOnBeforeToggleListener(element),
168
- on(element, EVENT_TOGGLE, event => {
172
+ on(element, 'toggle', event => {
169
173
  const {source} = event;
170
174
 
171
175
  if (element.open) {
172
176
  const modal =
173
- (source instanceof HTMLButtonElement && source.command === COMMAND_SHOW_MODAL) ||
174
- element.matches(SELECTOR_MODAL);
177
+ (source instanceof HTMLButtonElement && source.command === 'show-modal') ||
178
+ element.matches(':modal');
175
179
 
176
- element.setAttribute(ARIA_MODAL, String(modal));
177
- element.setAttribute(ATTRIBUTE_OPEN, '');
180
+ setAria(element, 'modal', modal);
181
+ setAttribute(element, ATTRIBUTE_OPEN, '');
178
182
 
179
183
  element.focus();
180
184
 
@@ -182,18 +186,20 @@ function getState(element: HTMLDialogElement): OuiDialogState {
182
186
  state.resizable?.set();
183
187
  }, 500);
184
188
  } else {
185
- element.removeAttribute(ATTRIBUTE_OPEN);
186
- element.setAttribute(ARIA_MODAL, String(state.modal));
189
+ setAttribute(element, ATTRIBUTE_OPEN);
190
+ setAria(element, 'modal', state.modal);
187
191
 
188
- element.style.inset = '';
189
- element.style.position = '';
190
- element.style.transform = '';
192
+ setStyles(element, {
193
+ inset: '',
194
+ position: '',
195
+ transform: '',
196
+ });
191
197
 
192
198
  state.resizable?.reset();
193
199
  }
194
200
  }),
195
201
  ],
196
- modal: element.getAttribute(ARIA_MODAL) === TRUE,
202
+ modal: getAria(element, 'modal') === 'true' || element.matches(':modal'),
197
203
  };
198
204
 
199
205
  state.dialog = new OuiDialog(state);
@@ -220,28 +226,18 @@ function removeDialog(element: HTMLElement): void {
220
226
 
221
227
  // #region Variables
222
228
 
223
- const ARIA_MODAL = 'aria-modal';
224
-
225
229
  const ATTRIBUTE = 'oui-dialog';
226
230
 
227
231
  const ATTRIBUTE_CONTAINER = `${ATTRIBUTE}-container`;
228
232
 
229
233
  const ATTRIBUTE_OPEN = `${ATTRIBUTE}-open`;
230
234
 
231
- const COMMAND_SHOW_MODAL = 'show-modal';
232
-
233
- const EVENT_TOGGLE = 'toggle';
234
-
235
235
  const MESSAGE = 'The element must be an instance of HTMLDialogElement';
236
236
 
237
237
  const SELECTOR_MOVER = `[${ATTRIBUTE}-mover]`;
238
238
 
239
- const SELECTOR_MODAL = ':modal';
240
-
241
239
  const SELECTOR_RESIZER = `[${ATTRIBUTE}-resizer]`;
242
240
 
243
- const TRUE = 'true';
244
-
245
241
  const states = new WeakMap<HTMLElement, OuiDialogState>();
246
242
 
247
243
  // #endregion
@@ -1,7 +1,10 @@
1
1
  import {isPlainObject} from '@oscarpalmer/atoms/is';
2
+ import {getAria} from '@oscarpalmer/toretto/aria';
3
+ import {getAttribute, setAttribute} from '@oscarpalmer/toretto/attribute';
2
4
  import {on} from '@oscarpalmer/toretto/event';
3
5
  import {isHTMLOrSVGElement} from '@oscarpalmer/toretto/is';
4
6
  import type {RemovableEventListener} from '@oscarpalmer/toretto/models';
7
+ import {setStyle, setStyles} from '@oscarpalmer/toretto/style';
5
8
 
6
9
  // #region Types
7
10
 
@@ -45,13 +48,15 @@ export class OuiFloatable {
45
48
  const {anchor, content} = state;
46
49
 
47
50
  if (standalone) {
48
- content.setAttribute(ATTRIBUTE_FLOATABLE, '');
51
+ setAttribute(content, ATTRIBUTE_FLOATABLE, '');
49
52
 
50
53
  content.popover = 'manual';
54
+ } else {
55
+ content.popover ??= 'auto';
51
56
  }
52
57
 
53
- anchor.style.anchorName = state.name;
54
- content.style.positionAnchor = state.name;
58
+ setStyle(anchor, 'anchorName', state.name);
59
+ setStyle(content, 'positionAnchor', state.name);
55
60
  }
56
61
 
57
62
  /**
@@ -211,11 +216,11 @@ function createInstance(
211
216
  export function getOnBeforeToggleListener(element: HTMLElement): RemovableEventListener {
212
217
  return on(
213
218
  element,
214
- EVENT_BEFORE,
219
+ 'beforetoggle',
215
220
  event => {
216
221
  const {newState, source} = event;
217
222
 
218
- if (newState === FLOATABLE_STATE_OPEN && source?.getAttribute(ARIA_DISABLED) === TRUE) {
223
+ if (newState === 'open' && source != null && getAria(source, 'disabled') === 'true') {
219
224
  event.preventDefault();
220
225
  }
221
226
  },
@@ -248,9 +253,11 @@ function getPosition(
248
253
  return options.position;
249
254
  }
250
255
 
251
- const attribute = anchor.getAttribute(options.attribute) ?? '';
256
+ const attribute = getAttribute(anchor, options.attribute);
252
257
 
253
- return attribute in AREAS ? (attribute as OuiFloatablePosition) : options.position;
258
+ return attribute != null && attribute in AREAS
259
+ ? (attribute as OuiFloatablePosition)
260
+ : options.position;
254
261
  }
255
262
 
256
263
  function getState(
@@ -281,10 +288,12 @@ function setPosition(state: OuiFloatableState, override?: OuiFloatablePosition):
281
288
 
282
289
  const area = AREAS[position];
283
290
 
284
- state.content.style.positionAnchor = state.name;
285
- state.content.style.positionArea = area;
291
+ setAttribute(state.content, ATTRIBUTE_POSITION, POSITIONS[area]);
286
292
 
287
- state.content.setAttribute(ATTRIBUTE_POSITION, POSITIONS[area]);
293
+ setStyles(state.content, {
294
+ positionAnchor: state.name,
295
+ positionArea: area,
296
+ });
288
297
  }
289
298
 
290
299
  // #endregion
@@ -306,24 +315,16 @@ const AREAS: Record<OuiFloatablePosition, string> = {
306
315
  'start-top': 'span-end start',
307
316
  };
308
317
 
309
- const ARIA_DISABLED = 'aria-disabled';
310
-
311
318
  export const ATTRIBUTE_FLOATABLE = 'oui-floatable';
312
319
 
313
320
  const ATTRIBUTE_POSITION = 'oui-position';
314
321
 
315
- const EVENT_BEFORE = 'beforetoggle';
316
-
317
- export const FLOATABLE_STATE_OPEN = 'open';
318
-
319
322
  const MESSAGE = 'Anchor and content must be an HTMLElement or SVGElement';
320
323
 
321
324
  const POSITIONS = Object.fromEntries(
322
325
  Object.entries(AREAS).map(([position, area]) => [area, position]),
323
326
  ) as Record<string, OuiFloatablePosition>;
324
327
 
325
- const TRUE = 'true';
326
-
327
328
  const instances = new WeakMap<HTMLElement, OuiFloatable>();
328
329
 
329
330
  let index = 0;
@@ -1,5 +1,6 @@
1
1
  import {isPlainObject} from '@oscarpalmer/atoms/is';
2
2
  import {clamp} from '@oscarpalmer/atoms/number';
3
+ import {setAttribute} from '@oscarpalmer/toretto/attribute';
3
4
  import {on} from '@oscarpalmer/toretto/event';
4
5
  import {findAncestor} from '@oscarpalmer/toretto/find';
5
6
  import {getFocusable} from '@oscarpalmer/toretto/focusable';
@@ -26,9 +27,9 @@ export class OuiFocusTrap {
26
27
  */
27
28
  set contain(value: boolean) {
28
29
  if (value === true) {
29
- this.#state.element.setAttribute(ATTRIBUTE_FOCUSTRAP_CONTAIN, '');
30
+ setAttribute(this.#state.element, ATTRIBUTE_FOCUSTRAP_CONTAIN, '');
30
31
  } else if (value === false) {
31
- this.#state.element.removeAttribute(ATTRIBUTE_FOCUSTRAP_CONTAIN);
32
+ setAttribute(this.#state.element, ATTRIBUTE_FOCUSTRAP_CONTAIN, undefined);
32
33
  }
33
34
  }
34
35
 
@@ -62,9 +63,9 @@ export class OuiFocusTrap {
62
63
  */
63
64
  set noescape(value: boolean) {
64
65
  if (value === true) {
65
- this.#state.element.setAttribute(ATTRIBUTE_FOCUSTRAP_NOESCAPE, '');
66
+ setAttribute(this.#state.element, ATTRIBUTE_FOCUSTRAP_NOESCAPE, '');
66
67
  } else if (value === false) {
67
- this.#state.element.removeAttribute(ATTRIBUTE_FOCUSTRAP_NOESCAPE);
68
+ setAttribute(this.#state.element, ATTRIBUTE_FOCUSTRAP_NOESCAPE, undefined);
68
69
  }
69
70
  }
70
71
 
@@ -74,7 +75,7 @@ export class OuiFocusTrap {
74
75
 
75
76
  state.element.tabIndex = -1;
76
77
 
77
- state.element.setAttribute(ATTRIBUTE_FOCUSTRAP, '');
78
+ setAttribute(state.element, ATTRIBUTE_FOCUSTRAP, '');
78
79
 
79
80
  this.contain = state.options.contain;
80
81
  this.noescape = state.options.noescape;
package/src/js/index.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import './menu';
1
2
  import './toolbar';
2
3
 
3
4
  export * from './dialog';
@@ -1,5 +1,3 @@
1
- import {isHTMLOrSVGElement} from '@oscarpalmer/toretto/is';
2
-
3
1
  export function attributable(
4
2
  attribute: string,
5
3
  add: (element: HTMLElement) => void,
@@ -11,7 +9,7 @@ export function attributable(
11
9
  for (let index = 0; index < length; index += 1) {
12
10
  const entry = entries[index];
13
11
 
14
- if (entry.type === 'attributes' && isHTMLOrSVGElement(entry.target)) {
12
+ if (entry.type === 'attributes' && entry.target instanceof Element) {
15
13
  if (entry.target.hasAttribute(attribute)) {
16
14
  handleAdded(attribute, [entry.target], add);
17
15
  } else {
@@ -41,7 +39,7 @@ function handleAdded(
41
39
  nodes: Node[] | NodeList,
42
40
  add: (element: HTMLElement) => void,
43
41
  ): void {
44
- const elements = [...nodes].filter(isHTMLOrSVGElement) as HTMLElement[];
42
+ const elements = [...nodes].filter(node => node instanceof Element) as HTMLElement[];
45
43
 
46
44
  const {length} = elements;
47
45
 
@@ -59,7 +57,7 @@ function handleAdded(
59
57
  }
60
58
 
61
59
  function handleRemoved(nodes: Node[] | NodeList, remove: (element: HTMLElement) => void): void {
62
- const elements = [...nodes].filter(isHTMLOrSVGElement) as HTMLElement[];
60
+ const elements = [...nodes].filter(node => node instanceof Element) as HTMLElement[];
63
61
 
64
62
  const {length} = elements;
65
63
 
@@ -1,12 +1,13 @@
1
1
  import type {EventPosition, PlainObject} from '@oscarpalmer/atoms/models';
2
2
  import {clamp} from '@oscarpalmer/atoms/number';
3
+ import {getAttribute, setAttribute} from '@oscarpalmer/toretto/attribute';
3
4
  import {getPosition, on} from '@oscarpalmer/toretto/event';
4
5
  import {findAncestor} from '@oscarpalmer/toretto/find';
5
- import {toggleStyles, type StyleToggler} from '@oscarpalmer/toretto/style';
6
+ import {isHTMLOrSVGElement} from '@oscarpalmer/toretto/is';
7
+ import {setStyle, setStyles, toggleStyles, type StyleToggler} from '@oscarpalmer/toretto/style';
6
8
  import supportsTouch from '@oscarpalmer/toretto/touch';
7
9
  import type {OuiMovable} from '../movable/movable.embedded';
8
10
  import type {OuiSortable} from '../sortable/sortable.embedded';
9
- import {isHTMLOrSVGElement} from '@oscarpalmer/toretto/is';
10
11
 
11
12
  // #region Types
12
13
 
@@ -63,7 +64,7 @@ export abstract class OuiDraggable<ExtraState extends PlainObject = PlainObject>
63
64
  setContainer(item);
64
65
  setDirection(item);
65
66
 
66
- element.setAttribute(attribute, '');
67
+ setAttribute(element, attribute, '');
67
68
 
68
69
  let states = globals.states.get(element);
69
70
 
@@ -425,9 +426,11 @@ function onPointerdown(event: MouseEvent | TouchEvent): void {
425
426
 
426
427
  const next = getNextPosition(item, position);
427
428
 
428
- dragged.style.position = 'fixed';
429
- dragged.style.inset = `${next.y}px auto auto ${next.x}px`;
430
- dragged.style.transform = 'translate3d(0, 0, 0)';
429
+ setStyles(dragged, {
430
+ position: 'fixed',
431
+ inset: `${next.y}px auto auto ${next.x}px`,
432
+ transform: 'translate3d(0, 0, 0)',
433
+ });
431
434
 
432
435
  item.state.options.drag.onBegin?.(event, globals, item, element, handle, next);
433
436
 
@@ -450,7 +453,7 @@ function onPointermove(event: MouseEvent | TouchEvent): void {
450
453
  const next = getNextPosition(current, position);
451
454
 
452
455
  if (globals.element != null) {
453
- globals.element.node.style.inset = `${next.y}px auto auto ${next.x}px`;
456
+ setStyle(globals.element.node, 'inset', `${next.y}px auto auto ${next.x}px`);
454
457
  }
455
458
 
456
459
  const detail = current.state.options.drag.onMove(event, globals, current, {
@@ -529,7 +532,7 @@ function setContainer(item: OuiDraggableItem): void {
529
532
  }
530
533
 
531
534
  const selector =
532
- container ?? item.instance.element.getAttribute(item.state.options.container.attribute);
535
+ container ?? getAttribute(item.instance.element, item.state.options.container.attribute);
533
536
 
534
537
  if (typeof selector !== 'string') {
535
538
  return;
@@ -559,7 +562,7 @@ function setContainerElement(item: OuiDraggableItem, element: HTMLElement): void
559
562
  }
560
563
 
561
564
  function setDirection(item: OuiDraggableItem): void {
562
- const direction = item.state.element.getAttribute(item.state.options.direction.attribute);
565
+ const direction = getAttribute(item.state.element, item.state.options.direction.attribute);
563
566
 
564
567
  if (horizontals.has(direction as OuiDraggableDirection)) {
565
568
  item.state.vertical = false;