@oscarpalmer/oui 0.24.0 → 0.26.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/dist/css/{tooltip.css → floatable.css} +59 -24
  2. package/dist/css/reset.css +17 -0
  3. package/dist/js/dialog.d.mts +38 -0
  4. package/dist/js/dialog.mjs +92 -0
  5. package/dist/js/embedded-CHK82Uh5.d.mts +76 -0
  6. package/dist/js/embedded-ai6cqURc.d.mts +68 -0
  7. package/dist/js/floatable/embedded.d.mts +2 -0
  8. package/dist/js/floatable/embedded.mjs +157 -0
  9. package/dist/js/floatable/standalone.d.mts +2 -0
  10. package/dist/js/floatable/standalone.mjs +2 -0
  11. package/dist/js/focus-trap/embedded.d.mts +2 -26
  12. package/dist/js/focus-trap/embedded.mjs +158 -80
  13. package/dist/js/focus-trap/standalone.d.mts +2 -1
  14. package/dist/js/focus-trap/standalone.mjs +3 -3
  15. package/dist/js/index.d.mts +6 -3
  16. package/dist/js/index.mjs +9 -5
  17. package/dist/js/internal/draggable.d.mts +2 -2
  18. package/dist/js/internal/draggable.mjs +3 -3
  19. package/dist/js/{movable-BKFgoaOl.d.mts → movable-dNqBctx3.d.mts} +16 -16
  20. package/dist/js/movable.d.mts +2 -2
  21. package/dist/js/movable.mjs +4 -4
  22. package/dist/js/popover.d.mts +38 -1
  23. package/dist/js/popover.mjs +83 -44
  24. package/dist/js/sortable.d.mts +2 -2
  25. package/dist/js/sortable.mjs +5 -5
  26. package/dist/js/splitter.mjs +49 -48
  27. package/dist/js/toolbar.d.mts +1 -0
  28. package/dist/js/toolbar.mjs +107 -0
  29. package/dist/js/tooltip.d.mts +27 -1
  30. package/dist/js/tooltip.mjs +85 -49
  31. package/package.json +14 -2
  32. package/src/css/{tooltip.scss → floatable.scss} +47 -5
  33. package/src/css/mixins.scss +0 -2
  34. package/src/css/reset.scss +17 -0
  35. package/src/js/dialog.ts +188 -0
  36. package/src/js/floatable/embedded.ts +323 -0
  37. package/src/js/floatable/standalone.ts +1 -0
  38. package/src/js/focus-trap/embedded.ts +219 -93
  39. package/src/js/focus-trap/standalone.ts +4 -2
  40. package/src/js/index.ts +6 -4
  41. package/src/js/internal/draggable.ts +20 -20
  42. package/src/js/movable.ts +17 -17
  43. package/src/js/popover.ts +143 -61
  44. package/src/js/sortable.ts +26 -26
  45. package/src/js/splitter.ts +98 -96
  46. package/src/js/toolbar.ts +214 -0
  47. package/src/js/tooltip.ts +138 -54
  48. package/dist/css/popover.css +0 -30
  49. package/dist/js/floatable.d.mts +0 -59
  50. package/dist/js/floatable.mjs +0 -130
  51. package/src/css/popover.scss +0 -10
  52. package/src/js/floatable.ts +0 -237
@@ -1,64 +1,103 @@
1
- import { createFloatable, removeFloatable } from "./floatable.mjs";
1
+ import { createEmbeddedFloatable, getOnBeforeToggleListener } from "./floatable/embedded.mjs";
2
2
  import { attributable } from "./internal/attributable.mjs";
3
- import { createFocusTrap, removeFocusTrap } from "./focus-trap/embedded.mjs";
4
- import { isHTMLOrSVGElement } from "@oscarpalmer/toretto/is";
3
+ import { createFocusTrap } from "./focus-trap/embedded.mjs";
5
4
  import { on } from "@oscarpalmer/toretto/event";
5
+ import { isHTMLOrSVGElement } from "@oscarpalmer/toretto/is";
6
6
  //#region src/js/popover.ts
7
- var Popover = class {
8
- element;
9
- anchor;
10
- floatable;
11
- focusTrap;
12
- listener;
13
- constructor(element) {
14
- this.element = element;
15
- const anchor = element.ownerDocument.querySelector(`[popovertarget="${element.id}"]`);
16
- if (!isHTMLOrSVGElement(anchor)) throw new Error(`No anchor found for popover with id '${element.id}'.`);
17
- if (element.contains(anchor)) throw new Error("Popover content cannot contain its anchor.");
18
- this.anchor = anchor;
19
- this.floatable = createFloatable(this.anchor, element, options, false);
20
- this.focusTrap = createFocusTrap(element, { noescape: true });
21
- this.listener = on(element, EVENT_TOGGLE, (event) => {
22
- if (event.newState === STATE_OPEN) {
23
- ignoreFocus = false;
24
- this.floatable?.update();
25
- element.focus();
26
- } else if (!ignoreFocus) this.anchor?.focus();
27
- });
28
- this.element.setAttribute(ATTRIBUTE_CONTENT, "");
29
- this.floatable?.update();
7
+ var OuiPopover = class {
8
+ #state;
9
+ /**
10
+ * Is the popover open?
11
+ */
12
+ get open() {
13
+ return this.#state.content.checkVisibility();
14
+ }
15
+ constructor(state) {
16
+ this.#state = state;
17
+ this.#state.floatable.update();
18
+ }
19
+ /**
20
+ * Closes the popover
21
+ */
22
+ hide() {
23
+ this.#state.floatable.hide();
30
24
  }
31
- destroy() {
32
- this.focusTrap?.destroy();
33
- removeFloatable(this.element);
34
- removeFocusTrap(this.element);
35
- this.element.removeAttribute(ATTRIBUTE_CONTENT);
36
- this.anchor = null;
37
- this.element = null;
25
+ /**
26
+ * Opens the popover
27
+ */
28
+ show() {
29
+ this.#state.floatable.show();
38
30
  }
39
31
  };
40
- function onAdd(element) {
41
- if (!instances.has(element) && element.getAttribute(ATTRIBUTE) !== POPOVER_HINT && !element.hasAttribute("oui-floatable")) instances.set(element, new Popover(element));
32
+ function addPopover(element) {
33
+ if (states.has(element) || element.hasAttribute("oui-floatable")) return;
34
+ const anchor = element.ownerDocument.querySelector(`[popovertarget="${element.id}"]`);
35
+ if (isHTMLOrSVGElement(anchor)) states.set(element, getState(anchor, element));
36
+ }
37
+ function destroyPopover(state) {
38
+ state.floatable.destroy();
39
+ state.focusTrap?.destroy();
40
+ for (const listener of state.listeners) listener();
41
+ state.anchor = void 0;
42
+ state.content = void 0;
43
+ state.floatable = void 0;
44
+ state.focusTrap = void 0;
45
+ state.listeners = void 0;
46
+ state.popover = void 0;
47
+ }
48
+ /**
49
+ * Get the _OuiPopover_ for an element
50
+ *
51
+ * @param element Element to get _OuiPopover_ for
52
+ * @returns _OuiPopover_ instance
53
+ */
54
+ function getPopover(element) {
55
+ return states.get(element)?.popover;
56
+ }
57
+ function getState(anchor, content) {
58
+ const state = {
59
+ anchor,
60
+ content,
61
+ floatable: createEmbeddedFloatable(anchor, content, options),
62
+ focusTrap: content.getAttribute(ARIA_MODAL) === TRUE ? createFocusTrap(content, { noescape: true }) : void 0,
63
+ listeners: [getOnBeforeToggleListener(content), on(content, EVENT_TOGGLE, (event) => {
64
+ if (event.newState === STATE_OPEN) {
65
+ ignoreFocus = false;
66
+ state.floatable?.update();
67
+ content.setAttribute(ATTRIBUTE_OPEN, "");
68
+ content.focus();
69
+ } else {
70
+ content.removeAttribute(ATTRIBUTE_OPEN);
71
+ if (!ignoreFocus) anchor?.focus();
72
+ }
73
+ })],
74
+ popover: void 0
75
+ };
76
+ state.popover = new OuiPopover(state);
77
+ return state;
42
78
  }
43
79
  function onPointerdown() {
44
80
  ignoreFocus = true;
45
81
  }
46
- function onRemove(element) {
47
- instances.get(element)?.destroy();
48
- instances.delete(element);
82
+ function removePopover(element) {
83
+ const state = states.get(element);
84
+ if (state == null) return;
85
+ destroyPopover(state);
86
+ states.delete(element);
49
87
  }
50
- const ATTRIBUTE = "popover";
51
- const ATTRIBUTE_CONTENT = "oui-popover-content";
88
+ const ATTRIBUTE = "oui-popover";
89
+ const ARIA_MODAL = "aria-modal";
90
+ const ATTRIBUTE_OPEN = `${ATTRIBUTE}-open`;
52
91
  const EVENT_TOGGLE = "toggle";
53
- const POPOVER_HINT = "hint";
54
92
  const STATE_OPEN = "open";
55
- const instances = /* @__PURE__ */ new WeakMap();
93
+ const TRUE = "true";
56
94
  const options = {
57
95
  attribute: `${ATTRIBUTE}position`,
58
96
  position: "below-start"
59
97
  };
98
+ const states = /* @__PURE__ */ new WeakMap();
60
99
  let ignoreFocus = false;
61
100
  on(document, "pointerdown", onPointerdown, { capture: true });
62
- attributable(ATTRIBUTE, onAdd, onRemove);
101
+ attributable(ATTRIBUTE, addPopover, removePopover);
63
102
  //#endregion
64
- export {};
103
+ export { getPopover };
@@ -1,2 +1,2 @@
1
- import { c as Sortable } from "./movable-BKFgoaOl.mjs";
2
- export { Sortable };
1
+ import { c as OuiSortable } from "./movable-dNqBctx3.mjs";
2
+ export { OuiSortable };
@@ -1,11 +1,11 @@
1
1
  import { attributable } from "./internal/attributable.mjs";
2
- import { Draggable, addDraggable, removeDraggable } from "./internal/draggable.mjs";
3
- import { isHTMLOrSVGElement } from "@oscarpalmer/toretto/is";
2
+ import { OuiDraggable, addDraggable, removeDraggable } from "./internal/draggable.mjs";
4
3
  import { getPosition } from "@oscarpalmer/toretto/event";
4
+ import { isHTMLOrSVGElement } from "@oscarpalmer/toretto/is";
5
5
  import { findAncestor, getElementFromPosition } from "@oscarpalmer/toretto/find";
6
6
  import { createElement } from "@oscarpalmer/toretto/create";
7
7
  //#region src/js/sortable.ts
8
- var Sortable = class extends Draggable {
8
+ var OuiSortable = class extends OuiDraggable {
9
9
  connections;
10
10
  origin;
11
11
  get dragged() {
@@ -230,7 +230,7 @@ const INSERT_BEFORE = "beforebegin";
230
230
  const SELECTOR = `[${ATTRIBUTE}]`;
231
231
  const SELECTOR_ITEM = `[${ATTRIBUTE}] > *`;
232
232
  const bound = {
233
- constructor: Sortable,
233
+ constructor: OuiSortable,
234
234
  property: "sortable"
235
235
  };
236
236
  const placeholder = {
@@ -242,4 +242,4 @@ const placeholder = {
242
242
  };
243
243
  attributable(ATTRIBUTE, addDraggable.bind(bound), removeDraggable.bind(bound));
244
244
  //#endregion
245
- export { Sortable };
245
+ export { OuiSortable };
@@ -1,6 +1,6 @@
1
+ import { getPosition, on } from "@oscarpalmer/toretto/event";
1
2
  import { isNullableOrWhitespace } from "@oscarpalmer/atoms/is";
2
3
  import { clamp } from "@oscarpalmer/atoms/number";
3
- import { getPosition, on } from "@oscarpalmer/toretto/event";
4
4
  import { findAncestor } from "@oscarpalmer/toretto/find";
5
5
  import { toggleStyles } from "@oscarpalmer/toretto/style";
6
6
  import supportsTouch from "@oscarpalmer/toretto/touch";
@@ -10,6 +10,50 @@ const ATTRIBUTE_MAX = "max";
10
10
  const ATTRIBUTE_MIN = "min";
11
11
  const ATTRIBUTE_SIZE = "size";
12
12
  const ATTRIBUTE_TYPE = "type";
13
+ var OuiSplitter = class {
14
+ element;
15
+ handle;
16
+ panels;
17
+ rectangle;
18
+ separator;
19
+ types;
20
+ values;
21
+ constructor(element, panels) {
22
+ this.element = element;
23
+ this.panels = panels;
24
+ if (isNullableOrWhitespace(element.id)) element.id = `oui_splitter_${++index}`;
25
+ this.values = getValues(element);
26
+ this.handle = createHandle();
27
+ this.rectangle = element.getBoundingClientRect();
28
+ this.separator = createSeparator(this);
29
+ this.separator.append(this.handle);
30
+ panels[0].insertAdjacentElement("afterend", this.separator);
31
+ this.updateOrientation();
32
+ this.updateValues();
33
+ }
34
+ updateConnections(add) {
35
+ const method = METHODS_CONNECT[add];
36
+ MAPPED_ELEMENTS[method](this.element, this);
37
+ MAPPED_ELEMENTS[method](this.handle, this);
38
+ MAPPED_ELEMENTS[method](this.separator, this);
39
+ if (this.types.auto) RESIZE_OBSERVER[METHODS_OBSERVE[add]](this.element);
40
+ }
41
+ updateOrientation() {
42
+ this.types = getTypes(this);
43
+ if (this.types.auto) RESIZE_OBSERVER.observe(this.element);
44
+ else RESIZE_OBSERVER.unobserve(this.element);
45
+ if (this.types.horizontal) this.element.setAttribute(ATTRIBUTE_HORIZONTAL, "");
46
+ else this.element.removeAttribute(ATTRIBUTE_HORIZONTAL);
47
+ setAriaOrientation(this);
48
+ }
49
+ updateValues() {
50
+ this.values = getValues(this.element);
51
+ requestAnimationFrame(() => {
52
+ setSize(this, this.values.now.current);
53
+ setAriaValue(this);
54
+ });
55
+ }
56
+ };
13
57
  var OuiSplitterElement = class extends HTMLElement {
14
58
  static observedAttributes = [
15
59
  ATTRIBUTE_MAX,
@@ -45,8 +89,8 @@ var OuiSplitterElement = class extends HTMLElement {
45
89
  constructor() {
46
90
  super();
47
91
  const panels = [...this.children].filter((child) => child instanceof HTMLElement);
48
- if (panels.length !== 2) throw new Error(`${SELECTOR} must have exactly two panels.`);
49
- this.#splitter = new Splitter(this, panels);
92
+ if (panels.length !== 2) throw new Error(MESSAGE);
93
+ this.#splitter = new OuiSplitter(this, panels);
50
94
  }
51
95
  connectedCallback() {
52
96
  this.#splitter.updateConnections(1);
@@ -68,51 +112,6 @@ var OuiSplitterElement = class extends HTMLElement {
68
112
  }
69
113
  }
70
114
  };
71
- let index = 0;
72
- var Splitter = class {
73
- element;
74
- handle;
75
- panels;
76
- rectangle;
77
- separator;
78
- types;
79
- values;
80
- constructor(element, panels) {
81
- this.element = element;
82
- this.panels = panels;
83
- if (isNullableOrWhitespace(element.id)) element.id = `oui_splitter_${++index}`;
84
- this.values = getValues(element);
85
- this.handle = createHandle();
86
- this.rectangle = element.getBoundingClientRect();
87
- this.separator = createSeparator(this);
88
- this.separator.append(this.handle);
89
- panels[0].insertAdjacentElement("afterend", this.separator);
90
- this.updateOrientation();
91
- this.updateValues();
92
- }
93
- updateConnections(add) {
94
- const method = METHODS_CONNECT[add];
95
- MAPPED_ELEMENTS[method](this.element, this);
96
- MAPPED_ELEMENTS[method](this.handle, this);
97
- MAPPED_ELEMENTS[method](this.separator, this);
98
- if (this.types.auto) RESIZE_OBSERVER[METHODS_OBSERVE[add]](this.element);
99
- }
100
- updateOrientation() {
101
- this.types = getTypes(this);
102
- if (this.types.auto) RESIZE_OBSERVER.observe(this.element);
103
- else RESIZE_OBSERVER.unobserve(this.element);
104
- if (this.types.horizontal) this.element.setAttribute(ATTRIBUTE_HORIZONTAL, "");
105
- else this.element.removeAttribute(ATTRIBUTE_HORIZONTAL);
106
- setAriaOrientation(this);
107
- }
108
- updateValues() {
109
- this.values = getValues(this.element);
110
- requestAnimationFrame(() => {
111
- setSize(this, this.values.now.current);
112
- setAriaValue(this);
113
- });
114
- }
115
- };
116
115
  function createHandle() {
117
116
  const handle = document.createElement("span");
118
117
  handle.setAttribute("aria-hidden", "true");
@@ -269,6 +268,7 @@ const KEYS_ALL = new Set([
269
268
  ...KEYS_VERTICAL
270
269
  ]);
271
270
  const MAPPED_ELEMENTS = /* @__PURE__ */ new WeakMap();
271
+ const MESSAGE = `<${SELECTOR}> must have exactly two panels.`;
272
272
  const METHODS_CONNECT = ["delete", "set"];
273
273
  const METHODS_OBSERVE = ["unobserve", "observe"];
274
274
  const NAVIGATION_MAXIMUM_PERCENTAGE = .9;
@@ -283,6 +283,7 @@ const STYLING_TOGGLER = toggleStyles(document.body, {
283
283
  webkitUserSelect: "none"
284
284
  });
285
285
  const VALUE_DECIMALS = 6;
286
+ let index = 0;
286
287
  let frame;
287
288
  let splitter;
288
289
  customElements.define(SELECTOR, OuiSplitterElement);
@@ -0,0 +1 @@
1
+ export { };
@@ -0,0 +1,107 @@
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";
6
+ //#region src/js/toolbar.ts
7
+ var OuiToolbar = class {
8
+ #state;
9
+ constructor(state) {
10
+ this.#state = state;
11
+ updateFocus(this.#state, false);
12
+ }
13
+ 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;
18
+ }
19
+ };
20
+ 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);
70
+ }
71
+ function removeToolbar(element) {
72
+ instances.get(element)?.destroy();
73
+ instances.delete(element);
74
+ }
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
+ 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 = new Set([KEY_END, KEY_HOME]);
92
+ const KEYS_HORIZONTAL = new Set([KEY_ARROW_RIGHT, "ArrowLeft"]);
93
+ const KEYS_VERTICAL = new Set([KEY_ARROW_DOWN, "ArrowUp"]);
94
+ const KEYS_ALL = new Set([
95
+ ...KEYS_ABSOLUTE,
96
+ ...KEYS_HORIZONTAL,
97
+ ...KEYS_VERTICAL
98
+ ]);
99
+ const KEYS_NEXT = new Set([KEY_ARROW_DOWN, KEY_ARROW_RIGHT]);
100
+ const ORIENTATION_HORIZONTAL = "horizontal";
101
+ const ORIENTATIONS = new Set([ORIENTATION_HORIZONTAL, "vertical"]);
102
+ const PROPERTY_DISABLED = "disabled";
103
+ const SELECTOR = `[${ATTRIBUTE}-item]`;
104
+ const instances = /* @__PURE__ */ new WeakMap();
105
+ attributable(ATTRIBUTE, addToolbar, removeToolbar);
106
+ //#endregion
107
+ export {};
@@ -1,4 +1,30 @@
1
+ import { n as OuiFloatable } from "./embedded-ai6cqURc.mjs";
2
+
1
3
  //#region src/js/tooltip.d.ts
4
+ declare class OuiTooltip {
5
+ #private;
6
+ /**
7
+ * Is the tooltip open?
8
+ */
9
+ get open(): boolean;
10
+ constructor(state: OuiTooltipState);
11
+ /**
12
+ * Closes the tooltip
13
+ */
14
+ hide(): void;
15
+ /**
16
+ * Opens the tooltip
17
+ */
18
+ show(): void;
19
+ }
20
+ type OuiTooltipState = {
21
+ anchor: HTMLElement;
22
+ content: HTMLElement;
23
+ floatable: OuiFloatable;
24
+ timer?: number;
25
+ tooltip: OuiTooltip;
26
+ };
27
+ declare function getTooltip(element: HTMLElement): OuiTooltip | undefined;
2
28
  /**
3
29
  * Set the delay before showing the tooltip, in milliseconds
4
30
  *
@@ -7,4 +33,4 @@
7
33
  */
8
34
  declare function setTooltipDelay(value: number): void;
9
35
  //#endregion
10
- export { setTooltipDelay };
36
+ export { type OuiTooltip, getTooltip, setTooltipDelay };
@@ -1,28 +1,34 @@
1
- import { Floatable } from "./floatable.mjs";
1
+ import { createEmbeddedFloatable } from "./floatable/embedded.mjs";
2
2
  import { attributable } from "./internal/attributable.mjs";
3
+ import { on } from "@oscarpalmer/toretto/event";
3
4
  import { isNullableOrWhitespace } from "@oscarpalmer/atoms/is";
4
5
  import { isHTMLOrSVGElement } from "@oscarpalmer/toretto/is";
5
- import { on } from "@oscarpalmer/toretto/event";
6
6
  import { findAncestor } from "@oscarpalmer/toretto/find";
7
7
  import { createElement } from "@oscarpalmer/toretto/create";
8
8
  //#region src/js/tooltip.ts
9
- var Tooltip = class {
10
- anchor;
11
- content;
12
- floatable;
13
- timer;
14
- constructor(anchor, content) {
15
- this.anchor = anchor;
16
- this.content = content;
17
- instances.set(content, this);
18
- anchor.insertAdjacentElement("afterend", content);
19
- this.floatable = new Floatable(anchor, content, options, false);
20
- this.floatable.update();
9
+ var OuiTooltip = class {
10
+ #state;
11
+ /**
12
+ * Is the tooltip open?
13
+ */
14
+ get open() {
15
+ return this.#state.content.checkVisibility();
16
+ }
17
+ constructor(state) {
18
+ this.#state = state;
19
+ state.floatable.update();
20
+ }
21
+ /**
22
+ * Closes the tooltip
23
+ */
24
+ hide() {
25
+ this.#state.floatable.hide();
21
26
  }
22
- destroy() {
23
- instances.delete(this.content);
24
- this.anchor = null;
25
- this.content = null;
27
+ /**
28
+ * Opens the tooltip
29
+ */
30
+ show() {
31
+ this.#state.floatable.show();
26
32
  }
27
33
  };
28
34
  function activate(event, focus) {
@@ -30,34 +36,50 @@ function activate(event, focus) {
30
36
  const content = findAncestor(event, SELECTOR_CONTENT);
31
37
  if (anchor == null && content == null) {
32
38
  if (!focused) {
33
- closeTooltip(active);
34
- active = void 0;
39
+ closeTooltip(activeState);
40
+ activeState = void 0;
41
+ activeTooltip = void 0;
35
42
  }
36
43
  return;
37
44
  }
38
- const instance = instances.get(anchor ?? content);
39
- if (instance == null || instance === active) return;
40
- const shouldDelay = active == null;
41
- closeTooltip(active);
42
- active = instance;
45
+ const state = states.get(anchor ?? content);
46
+ if (state == null || state.tooltip === activeTooltip) return;
47
+ const shouldDelay = activeTooltip == null;
48
+ closeTooltip(activeState);
49
+ activeState = state;
50
+ activeTooltip = state.tooltip;
43
51
  focused = focus;
44
- instance.timer = setTimeout(() => {
45
- instance.content.showPopover({ source: instance.anchor });
52
+ state.timer = setTimeout(() => {
53
+ state.content.showPopover({ source: state.anchor });
46
54
  setTimeout(() => {
47
- instance.floatable.update();
48
- instance.content.setAttribute(ATTRIBUTE_CONTENT_ACTIVE, "");
55
+ state.floatable.update();
56
+ state.content.setAttribute(ATTRIBUTE_CONTENT_OPEN, "");
49
57
  });
50
58
  }, shouldDelay ? delay : 0);
51
59
  }
52
- function closeTooltip(instance) {
53
- if (instance == null) return;
54
- if (instance.timer != null) clearTimeout(instance.timer);
55
- instance.content?.hidePopover();
56
- instance.content?.removeAttribute(ATTRIBUTE_CONTENT_ACTIVE);
60
+ function addTooltip(element) {
61
+ if (!states.has(element)) createTooltip(element);
62
+ }
63
+ function closeTooltip(state) {
64
+ if (state == null) return;
65
+ if (state.timer != null) clearTimeout(state.timer);
66
+ state.content?.hidePopover();
67
+ state.content?.removeAttribute(ATTRIBUTE_CONTENT_OPEN);
57
68
  }
58
69
  function createTooltip(anchor) {
59
70
  const content = getContent(anchor);
60
- if (content != null) instances.set(anchor, new Tooltip(anchor, content));
71
+ if (content == null) return;
72
+ anchor.insertAdjacentElement("afterend", content);
73
+ states.set(anchor, getState(anchor, content));
74
+ }
75
+ function destroyTooltip(state) {
76
+ clearTimeout(state.timer);
77
+ state.content.remove();
78
+ state.floatable.destroy();
79
+ state.anchor = void 0;
80
+ state.content = void 0;
81
+ state.floatable = void 0;
82
+ state.tooltip = void 0;
61
83
  }
62
84
  function findElements(attribute) {
63
85
  const ids = attribute?.split(EXPRESSION_WHITESPACE) ?? [];
@@ -97,27 +119,40 @@ function getElements(anchor) {
97
119
  paragraph.textContent = content;
98
120
  return [paragraph];
99
121
  }
100
- function onAdd(element) {
101
- if (!instances.has(element)) createTooltip(element);
122
+ function getState(anchor, content) {
123
+ const state = {
124
+ anchor,
125
+ content,
126
+ floatable: createEmbeddedFloatable(anchor, content, options),
127
+ tooltip: null
128
+ };
129
+ state.tooltip = new OuiTooltip(state);
130
+ return state;
131
+ }
132
+ function getTooltip(element) {
133
+ return states.get(element)?.tooltip;
102
134
  }
103
135
  function onFocusin(event) {
104
136
  activate(event, true);
105
137
  }
106
138
  function onFocusout() {
107
- if (active == null) return;
139
+ if (activeTooltip == null) return;
108
140
  if (focusTimer != null) clearTimeout(focusTimer);
109
141
  focusTimer = setTimeout(() => {
110
- if (active == null || document.activeElement == null || findAncestor(document.activeElement, SELECTOR) != null) return;
111
- closeTooltip(active);
112
- active = void 0;
142
+ if (activeTooltip == null || document.activeElement == null || findAncestor(document.activeElement, SELECTOR) != null) return;
143
+ closeTooltip(activeState);
144
+ activeState = void 0;
145
+ activeTooltip = void 0;
113
146
  });
114
147
  }
115
148
  function onPointermove(event) {
116
149
  activate(event, false);
117
150
  }
118
- function onRemove(element) {
119
- instances.get(element)?.destroy();
120
- instances.delete(element);
151
+ function removeTooltip(element) {
152
+ const state = states.get(element);
153
+ if (state == null) return;
154
+ destroyTooltip(state);
155
+ states.delete(element);
121
156
  }
122
157
  /**
123
158
  * Set the delay before showing the tooltip, in milliseconds
@@ -136,21 +171,22 @@ const CONTENT_TAGNAME = "div";
136
171
  const EXPRESSION_WHITESPACE = /\s+/;
137
172
  const ATTRIBUTE = "oui-tooltip";
138
173
  const ATTRIBUTE_CONTENT = `${ATTRIBUTE}-content`;
139
- const ATTRIBUTE_CONTENT_ACTIVE = `${ATTRIBUTE_CONTENT}-active`;
174
+ const ATTRIBUTE_CONTENT_OPEN = `${ATTRIBUTE_CONTENT}-open`;
140
175
  const SELECTOR = `[${ATTRIBUTE}]`;
141
176
  const SELECTOR_CONTENT = `[${ATTRIBUTE_CONTENT}]`;
142
- const instances = /* @__PURE__ */ new WeakMap();
177
+ const states = /* @__PURE__ */ new WeakMap();
143
178
  const options = {
144
179
  attribute: `${ATTRIBUTE}-position`,
145
180
  position: "above"
146
181
  };
147
182
  let delay = 500;
148
183
  let focused = false;
149
- let active;
184
+ let activeState;
185
+ let activeTooltip;
150
186
  let focusTimer;
151
187
  on(document, "focusin", onFocusin);
152
188
  on(document, "focusout", onFocusout);
153
189
  on(document, "pointermove", onPointermove);
154
- attributable(ATTRIBUTE, onAdd, onRemove);
190
+ attributable(ATTRIBUTE, addTooltip, removeTooltip);
155
191
  //#endregion
156
- export { setTooltipDelay };
192
+ export { getTooltip, setTooltipDelay };