@oscarpalmer/oui 0.26.0 → 0.27.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 (66) hide show
  1. package/dist/css/floatable.css +0 -2
  2. package/dist/css/message.css +1 -0
  3. package/dist/css/reset.css +1 -0
  4. package/dist/js/dialog.d.mts +6 -0
  5. package/dist/js/dialog.mjs +23 -1
  6. package/dist/js/draggable-zszaW1qS.d.mts +155 -0
  7. package/dist/js/floatable/floatable.embedded.d.mts +2 -0
  8. package/dist/js/floatable/{embedded.mjs → floatable.embedded.mjs} +22 -17
  9. package/dist/js/floatable/floatable.standalone.d.mts +2 -0
  10. package/dist/js/floatable/floatable.standalone.mjs +2 -0
  11. package/dist/js/{embedded-ai6cqURc.d.mts → floatable.embedded-YxGwzgbE.d.mts} +13 -10
  12. package/dist/js/focus-trap/focus-trap.embedded.d.mts +2 -0
  13. package/dist/js/focus-trap/{embedded.mjs → focus-trap.embedded.mjs} +17 -15
  14. package/dist/js/focus-trap/focus-trap.standalone.d.mts +2 -0
  15. package/dist/js/focus-trap/focus-trap.standalone.mjs +2 -0
  16. package/dist/js/{embedded-CHK82Uh5.d.mts → focus-trap.embedded-DR8NpZfr.d.mts} +18 -18
  17. package/dist/js/index.d.mts +4 -3
  18. package/dist/js/index.mjs +8 -7
  19. package/dist/js/internal/draggable.d.mts +2 -2
  20. package/dist/js/internal/draggable.mjs +146 -94
  21. package/dist/js/movable/movable.embedded.d.mts +2 -0
  22. package/dist/js/movable/movable.embedded.mjs +184 -0
  23. package/dist/js/movable/movable.standalone.d.mts +2 -0
  24. package/dist/js/movable/movable.standalone.mjs +2 -0
  25. package/dist/js/popover.d.mts +3 -2
  26. package/dist/js/popover.mjs +8 -3
  27. package/dist/js/sortable/sortable.embedded.d.mts +2 -0
  28. package/dist/js/{sortable.mjs → sortable/sortable.embedded.mjs} +88 -63
  29. package/dist/js/sortable/sortable.standalone.d.mts +2 -0
  30. package/dist/js/sortable/sortable.standalone.mjs +2 -0
  31. package/dist/js/splitter.d.mts +24 -0
  32. package/dist/js/splitter.mjs +33 -9
  33. package/dist/js/toolbar.mjs +6 -6
  34. package/dist/js/tooltip.d.mts +12 -2
  35. package/dist/js/tooltip.mjs +49 -33
  36. package/package.json +11 -11
  37. package/src/css/floatable.scss +0 -2
  38. package/src/css/message.scss +1 -0
  39. package/src/css/reset.scss +1 -0
  40. package/src/js/dialog.ts +42 -1
  41. package/src/js/floatable/{embedded.ts → floatable.embedded.ts} +26 -18
  42. package/src/js/floatable/floatable.standalone.ts +6 -0
  43. package/src/js/focus-trap/{embedded.ts → focus-trap.embedded.ts} +20 -17
  44. package/src/js/focus-trap/focus-trap.standalone.ts +1 -0
  45. package/src/js/index.ts +4 -4
  46. package/src/js/internal/draggable.ts +309 -168
  47. package/src/js/movable/movable.embedded.ts +328 -0
  48. package/src/js/movable/movable.standalone.ts +2 -0
  49. package/src/js/popover.ts +11 -2
  50. package/src/js/{sortable.ts → sortable/sortable.embedded.ts} +170 -103
  51. package/src/js/sortable/sortable.standalone.ts +2 -0
  52. package/src/js/splitter.ts +28 -4
  53. package/src/js/tooltip.ts +66 -43
  54. package/dist/js/floatable/embedded.d.mts +0 -2
  55. package/dist/js/floatable/standalone.d.mts +0 -2
  56. package/dist/js/floatable/standalone.mjs +0 -2
  57. package/dist/js/focus-trap/embedded.d.mts +0 -2
  58. package/dist/js/focus-trap/standalone.d.mts +0 -2
  59. package/dist/js/focus-trap/standalone.mjs +0 -6
  60. package/dist/js/movable-dNqBctx3.d.mts +0 -101
  61. package/dist/js/movable.d.mts +0 -2
  62. package/dist/js/movable.mjs +0 -152
  63. package/dist/js/sortable.d.mts +0 -2
  64. package/src/js/floatable/standalone.ts +0 -1
  65. package/src/js/focus-trap/standalone.ts +0 -6
  66. package/src/js/movable.ts +0 -260
@@ -1,4 +1,4 @@
1
- import { n as OuiFloatable } from "./embedded-ai6cqURc.mjs";
1
+ import { i as OuiFloatable } from "./floatable.embedded-YxGwzgbE.mjs";
2
2
 
3
3
  //#region src/js/tooltip.d.ts
4
4
  declare class OuiTooltip {
@@ -7,6 +7,10 @@ declare class OuiTooltip {
7
7
  * Is the tooltip open?
8
8
  */
9
9
  get open(): boolean;
10
+ /**
11
+ * Open or close the tooltip
12
+ */
13
+ set open(value: boolean);
10
14
  constructor(state: OuiTooltipState);
11
15
  /**
12
16
  * Closes the tooltip
@@ -19,11 +23,17 @@ declare class OuiTooltip {
19
23
  }
20
24
  type OuiTooltipState = {
21
25
  anchor: HTMLElement;
22
- content: HTMLElement;
26
+ elements: HTMLElement[];
23
27
  floatable: OuiFloatable;
24
28
  timer?: number;
25
29
  tooltip: OuiTooltip;
26
30
  };
31
+ /**
32
+ * Get the _OuiTooltip_ for an element
33
+ *
34
+ * @param element Element to get _OuiTooltip_ for
35
+ * @returns _OuiTooltip_ instance
36
+ */
27
37
  declare function getTooltip(element: HTMLElement): OuiTooltip | undefined;
28
38
  /**
29
39
  * Set the delay before showing the tooltip, in milliseconds
@@ -1,4 +1,4 @@
1
- import { createEmbeddedFloatable } from "./floatable/embedded.mjs";
1
+ import { createEmbeddedFloatable } from "./floatable/floatable.embedded.mjs";
2
2
  import { attributable } from "./internal/attributable.mjs";
3
3
  import { on } from "@oscarpalmer/toretto/event";
4
4
  import { isNullableOrWhitespace } from "@oscarpalmer/atoms/is";
@@ -12,7 +12,14 @@ var OuiTooltip = class {
12
12
  * Is the tooltip open?
13
13
  */
14
14
  get open() {
15
- return this.#state.content.checkVisibility();
15
+ return this === activeTooltip && content.checkVisibility();
16
+ }
17
+ /**
18
+ * Open or close the tooltip
19
+ */
20
+ set open(value) {
21
+ if (value === true) this.show();
22
+ else if (value === false) this.hide();
16
23
  }
17
24
  constructor(state) {
18
25
  this.#state = state;
@@ -32,52 +39,50 @@ var OuiTooltip = class {
32
39
  }
33
40
  };
34
41
  function activate(event, focus) {
35
- const anchor = findAncestor(event, SELECTOR);
36
- const content = findAncestor(event, SELECTOR_CONTENT);
37
- if (anchor == null && content == null) {
42
+ const targetAnchor = findAncestor(event, SELECTOR);
43
+ const targetContent = findAncestor(event, SELECTOR_CONTENT);
44
+ if (targetAnchor == null && targetContent == null) {
38
45
  if (!focused) {
39
- closeTooltip(activeState);
46
+ closeTooltip(true, activeState);
40
47
  activeState = void 0;
41
48
  activeTooltip = void 0;
42
49
  }
43
50
  return;
44
51
  }
45
- const state = states.get(anchor ?? content);
52
+ const state = states.get(targetAnchor ?? targetContent);
46
53
  if (state == null || state.tooltip === activeTooltip) return;
47
54
  const shouldDelay = activeTooltip == null;
48
- closeTooltip(activeState);
55
+ closeTooltip(false, activeState);
49
56
  activeState = state;
50
57
  activeTooltip = state.tooltip;
51
58
  focused = focus;
52
59
  state.timer = setTimeout(() => {
53
- state.content.showPopover({ source: state.anchor });
60
+ content.innerHTML = "";
61
+ content.append(...state.elements);
62
+ state.floatable.show();
54
63
  setTimeout(() => {
55
64
  state.floatable.update();
56
- state.content.setAttribute(ATTRIBUTE_CONTENT_OPEN, "");
57
65
  });
58
66
  }, shouldDelay ? delay : 0);
59
67
  }
60
68
  function addTooltip(element) {
61
69
  if (!states.has(element)) createTooltip(element);
62
70
  }
63
- function closeTooltip(state) {
71
+ function closeTooltip(clear, state) {
64
72
  if (state == null) return;
65
73
  if (state.timer != null) clearTimeout(state.timer);
66
- state.content?.hidePopover();
67
- state.content?.removeAttribute(ATTRIBUTE_CONTENT_OPEN);
74
+ if (clear) content?.hidePopover();
68
75
  }
69
76
  function createTooltip(anchor) {
70
- const content = getContent(anchor);
71
- if (content == null) return;
72
- anchor.insertAdjacentElement("afterend", content);
73
- states.set(anchor, getState(anchor, content));
77
+ const elements = getElements(anchor);
78
+ if (elements == null) return;
79
+ states.set(anchor, getState(anchor, elements));
74
80
  }
75
81
  function destroyTooltip(state) {
76
82
  clearTimeout(state.timer);
77
- state.content.remove();
78
83
  state.floatable.destroy();
79
84
  state.anchor = void 0;
80
- state.content = void 0;
85
+ state.elements = void 0;
81
86
  state.floatable = void 0;
82
87
  state.tooltip = void 0;
83
88
  }
@@ -95,40 +100,43 @@ function findElements(attribute) {
95
100
  }
96
101
  return elements;
97
102
  }
98
- function getContent(anchor) {
99
- const elements = getElements(anchor);
100
- if (elements == null) return;
103
+ function getContent() {
101
104
  const content = createElement(CONTENT_TAGNAME, {
102
105
  popover: "hint",
103
106
  role: "tooltip",
104
107
  tabIndex: -1
105
108
  });
106
109
  content.setAttribute(ATTRIBUTE_CONTENT, "");
107
- content.append(...elements);
108
110
  return content;
109
111
  }
110
112
  function getElements(anchor) {
111
113
  let elements = findElements(anchor.getAttribute(ARIA_LABELLEDBY));
112
- if (elements.length > 0) return elements.map((element) => element.cloneNode(true));
114
+ if (elements.length > 0) return elements;
113
115
  elements = findElements(anchor.getAttribute(ARIA_DESCRIBEDBY));
114
- if (elements.length > 0) return elements.map((element) => element.cloneNode(true));
115
- let content = anchor.getAttribute(ARIA_LABEL);
116
- if (isNullableOrWhitespace(content)) content = anchor.getAttribute(ARIA_DESCRIPTION);
117
- if (isNullableOrWhitespace(content)) return;
116
+ if (elements.length > 0) return elements;
117
+ let text = anchor.getAttribute(ARIA_LABEL);
118
+ if (isNullableOrWhitespace(text)) text = anchor.getAttribute(ARIA_DESCRIPTION);
119
+ if (isNullableOrWhitespace(text)) return;
118
120
  const paragraph = document.createElement("p");
119
- paragraph.textContent = content;
121
+ paragraph.textContent = text;
120
122
  return [paragraph];
121
123
  }
122
- function getState(anchor, content) {
124
+ function getState(anchor, elements) {
123
125
  const state = {
124
126
  anchor,
125
- content,
127
+ elements,
126
128
  floatable: createEmbeddedFloatable(anchor, content, options),
127
129
  tooltip: null
128
130
  };
129
131
  state.tooltip = new OuiTooltip(state);
130
132
  return state;
131
133
  }
134
+ /**
135
+ * Get the _OuiTooltip_ for an element
136
+ *
137
+ * @param element Element to get _OuiTooltip_ for
138
+ * @returns _OuiTooltip_ instance
139
+ */
132
140
  function getTooltip(element) {
133
141
  return states.get(element)?.tooltip;
134
142
  }
@@ -140,7 +148,7 @@ function onFocusout() {
140
148
  if (focusTimer != null) clearTimeout(focusTimer);
141
149
  focusTimer = setTimeout(() => {
142
150
  if (activeTooltip == null || document.activeElement == null || findAncestor(document.activeElement, SELECTOR) != null) return;
143
- closeTooltip(activeState);
151
+ closeTooltip(true, activeState);
144
152
  activeState = void 0;
145
153
  activeTooltip = void 0;
146
154
  });
@@ -148,6 +156,10 @@ function onFocusout() {
148
156
  function onPointermove(event) {
149
157
  activate(event, false);
150
158
  }
159
+ function onToggle(event) {
160
+ if (event.newState === "open") content.setAttribute(ATTRIBUTE_CONTENT_OPEN, "");
161
+ else content.removeAttribute(ATTRIBUTE_CONTENT_OPEN);
162
+ }
151
163
  function removeTooltip(element) {
152
164
  const state = states.get(element);
153
165
  if (state == null) return;
@@ -174,16 +186,20 @@ const ATTRIBUTE_CONTENT = `${ATTRIBUTE}-content`;
174
186
  const ATTRIBUTE_CONTENT_OPEN = `${ATTRIBUTE_CONTENT}-open`;
175
187
  const SELECTOR = `[${ATTRIBUTE}]`;
176
188
  const SELECTOR_CONTENT = `[${ATTRIBUTE_CONTENT}]`;
189
+ const content = getContent();
177
190
  const states = /* @__PURE__ */ new WeakMap();
178
191
  const options = {
179
192
  attribute: `${ATTRIBUTE}-position`,
180
- position: "above"
193
+ position: "above",
194
+ reusable: true
181
195
  };
182
196
  let delay = 500;
183
197
  let focused = false;
184
198
  let activeState;
185
199
  let activeTooltip;
186
200
  let focusTimer;
201
+ document.body.append(content);
202
+ on(content, "toggle", onToggle);
187
203
  on(document, "focusin", onFocusin);
188
204
  on(document, "focusout", onFocusout);
189
205
  on(document, "pointermove", onPointermove);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oscarpalmer/oui",
3
- "version": "0.26.0",
3
+ "version": "0.27.0",
4
4
  "description": "Styles, components, and utilities for building websites and applications.",
5
5
  "keywords": [
6
6
  "css",
@@ -38,24 +38,24 @@
38
38
  "default": "./dist/js/dialog.mjs"
39
39
  },
40
40
  "./floatable": {
41
- "types": "./dist/js/floatable/standalone.d.mts",
42
- "default": "./dist/js/floatable/standalone.mjs"
41
+ "types": "./dist/js/floatable/floatable.standalone.d.mts",
42
+ "default": "./dist/js/floatable/floatable.standalone.mjs"
43
43
  },
44
44
  "./focus-trap": {
45
- "types": "./dist/js/focus-trap/standalone.d.mts",
46
- "default": "./dist/js/focus-trap/standalone.mjs"
45
+ "types": "./dist/js/focus-trap/focus-trap.standalone.d.mts",
46
+ "default": "./dist/js/focus-trap/focus-trap.standalone.mjs"
47
47
  },
48
48
  "./movable": {
49
- "types": "./dist/js/movable.d.mts",
50
- "default": "./dist/js/movable.mjs"
49
+ "types": "./dist/js/movable/movable.standalone.d.mts",
50
+ "default": "./dist/js/movable/movable.standalone.mjs"
51
51
  },
52
52
  "./popover": {
53
53
  "types": "./dist/js/popover.d.mts",
54
54
  "default": "./dist/js/popover.mjs"
55
55
  },
56
56
  "./sortable": {
57
- "types": "./dist/js/sortable.d.mts",
58
- "default": "./dist/js/sortable.mjs"
57
+ "types": "./dist/js/sortable/sortable.standalone.d.mts",
58
+ "default": "./dist/js/sortable/sortable.standalone.mjs"
59
59
  },
60
60
  "./splitter": {
61
61
  "types": "./dist/js/splitter.d.mts",
@@ -86,7 +86,7 @@
86
86
  "@oscarpalmer/toretto": "^0.46"
87
87
  },
88
88
  "devDependencies": {
89
- "@oxlint/plugins": "^1.70",
89
+ "@oxlint/plugins": "^1.72",
90
90
  "@vitest/coverage-istanbul": "^4.1",
91
91
  "autoprefixer": "^10.5",
92
92
  "concurrently": "^10",
@@ -95,7 +95,7 @@
95
95
  "postcss-input-range": "^6",
96
96
  "postcss-scss": "^4",
97
97
  "sass": "^1.101",
98
- "stylelint": "^17.13",
98
+ "stylelint": "^17.14",
99
99
  "stylelint-config-standard-scss": "^17",
100
100
  "tsdown": "^0.22",
101
101
  "typescript": "^6",
@@ -77,8 +77,6 @@
77
77
  :where([oui-tooltip-content]) {
78
78
  max-width: 48ch;
79
79
  padding: 0.75em 1em;
80
- overflow: visible;
81
- opacity: 0;
82
80
  background-color: var(--oui-absolute);
83
81
  border-radius: 0.25rem;
84
82
  font-size: 0.875rem;
@@ -24,6 +24,7 @@
24
24
  }
25
25
 
26
26
  :where(& h2, & h3, & h4, & h5, & h6) {
27
+ color: var(--oui-foreground, var(--oui-gray-10));
27
28
  font-size: 1em;
28
29
  font-weight: bold;
29
30
  }
@@ -123,6 +123,7 @@
123
123
 
124
124
  :where(dialog) {
125
125
  display: none !important;
126
+ overflow: visible;
126
127
  overscroll-behavior: contain;
127
128
  border: none;
128
129
  color: currentcolor;
package/src/js/dialog.ts CHANGED
@@ -1,7 +1,9 @@
1
1
  import {on} from '@oscarpalmer/toretto/event';
2
2
  import type {RemovableEventListener} from '@oscarpalmer/toretto/models';
3
- import {getOnBeforeToggleListener} from './floatable/embedded';
3
+ import {getOnBeforeToggleListener} from './floatable/floatable.embedded';
4
4
  import {attributable} from './internal/attributable';
5
+ import {ATTRIBUTE_MOVABLE_HANDLE} from './movable/movable.embedded';
6
+ import {createMovable, type OuiMovable} from './movable/movable.standalone';
5
7
 
6
8
  // #region Types
7
9
 
@@ -15,6 +17,17 @@ class OuiDialog {
15
17
  return this.#state.element.open;
16
18
  }
17
19
 
20
+ /**
21
+ * Open or close the dialog
22
+ */
23
+ set open(value: boolean) {
24
+ if (value === true) {
25
+ this.show();
26
+ } else if (value === false) {
27
+ this.hide();
28
+ }
29
+ }
30
+
18
31
  constructor(state: OuiDialogState) {
19
32
  this.#state = state;
20
33
  }
@@ -68,6 +81,7 @@ type OuiDialogState = {
68
81
  element: HTMLDialogElement;
69
82
  listeners: RemovableEventListener[];
70
83
  modal: boolean;
84
+ movable?: OuiMovable;
71
85
  };
72
86
 
73
87
  // #endregion
@@ -109,6 +123,22 @@ function getDialog(element: HTMLDialogElement): OuiDialog | undefined {
109
123
  return states.get(element)?.dialog;
110
124
  }
111
125
 
126
+ function getMovable(element: HTMLDialogElement): OuiMovable | undefined {
127
+ const handles = element.querySelectorAll(SELECTOR_MOVER);
128
+
129
+ if (handles.length === 0) {
130
+ return;
131
+ }
132
+
133
+ for (const handle of handles) {
134
+ handle.setAttribute(ATTRIBUTE_MOVABLE_HANDLE, '');
135
+ }
136
+
137
+ return createMovable(element, {
138
+ container: element.getAttribute(ATTRIBUTE_CONTAINER) ?? undefined,
139
+ });
140
+ }
141
+
112
142
  function getState(element: HTMLDialogElement): OuiDialogState {
113
143
  const state: OuiDialogState = {
114
144
  element,
@@ -125,9 +155,15 @@ function getState(element: HTMLDialogElement): OuiDialogState {
125
155
 
126
156
  element.setAttribute(ARIA_MODAL, String(modal));
127
157
  element.setAttribute(ATTRIBUTE_OPEN, '');
158
+
159
+ element.focus();
128
160
  } else {
129
161
  element.removeAttribute(ATTRIBUTE_OPEN);
130
162
  element.setAttribute(ARIA_MODAL, String(state.modal));
163
+
164
+ element.style.inset = '';
165
+ element.style.position = '';
166
+ element.style.transform = '';
131
167
  }
132
168
  }),
133
169
  ],
@@ -135,6 +171,7 @@ function getState(element: HTMLDialogElement): OuiDialogState {
135
171
  };
136
172
 
137
173
  state.dialog = new OuiDialog(state);
174
+ state.movable = getMovable(element);
138
175
 
139
176
  return state;
140
177
  }
@@ -159,6 +196,8 @@ const ARIA_MODAL = 'aria-modal';
159
196
 
160
197
  const ATTRIBUTE = 'oui-dialog';
161
198
 
199
+ const ATTRIBUTE_CONTAINER = `${ATTRIBUTE}-container`;
200
+
162
201
  const ATTRIBUTE_OPEN = `${ATTRIBUTE}-open`;
163
202
 
164
203
  const COMMAND_SHOW_MODAL = 'show-modal';
@@ -167,6 +206,8 @@ const EVENT_TOGGLE = 'toggle';
167
206
 
168
207
  const MESSAGE = 'The element must be an instance of HTMLDialogElement';
169
208
 
209
+ const SELECTOR_MOVER = `[${ATTRIBUTE}-mover]`;
210
+
170
211
  const SELECTOR_MODAL = ':modal';
171
212
 
172
213
  const TRUE = 'true';
@@ -5,13 +5,13 @@ import type {RemovableEventListener} from '@oscarpalmer/toretto/models';
5
5
 
6
6
  // #region Types
7
7
 
8
- type CreateOuiFloatableOptions = {
8
+ export type CreateOuiFloatableOptions = {
9
9
  /**
10
- * Open the _OuiFloatable_ when created? _(defaults to `true`)_
10
+ * Open the floatable when created? _(defaults to `true`)_
11
11
  */
12
12
  open?: boolean;
13
13
  /**
14
- * Position of the _OuiFloatable_ _(defaults to `below`)_
14
+ * Position of the floatable _(defaults to `below`)_
15
15
  */
16
16
  position?: OuiFloatablePosition;
17
17
  };
@@ -22,14 +22,14 @@ export class OuiFloatable {
22
22
  readonly #state: OuiFloatableState;
23
23
 
24
24
  /**
25
- * Is the _OuiFloatable_ open?
25
+ * Is the floatable open?
26
26
  */
27
27
  get open(): boolean {
28
28
  return this.#state.content.checkVisibility();
29
29
  }
30
30
 
31
31
  /**
32
- * Open or close the _OuiFloatable_
32
+ * Open or close the floatable
33
33
  */
34
34
  set open(value: boolean) {
35
35
  if (value === true) {
@@ -50,14 +50,12 @@ export class OuiFloatable {
50
50
  content.popover = 'manual';
51
51
  }
52
52
 
53
- const anchorName = getAnchorName(anchor);
54
-
55
- anchor.style.anchorName = anchorName;
56
- content.style.positionAnchor = anchorName;
53
+ anchor.style.anchorName = state.name;
54
+ content.style.positionAnchor = state.name;
57
55
  }
58
56
 
59
57
  /**
60
- * Destroy the _OuiFloatable_, removing it from the DOM and cleaning up references
58
+ * Destroy the floatable, removing it from the DOM and cleaning up references
61
59
  */
62
60
  destroy(): void {
63
61
  if (this.#destroyed) {
@@ -73,7 +71,7 @@ export class OuiFloatable {
73
71
  }
74
72
 
75
73
  /**
76
- * Closes the _OuiFloatable_
74
+ * Closes the floatable
77
75
  */
78
76
  hide(): void {
79
77
  if (!this.#destroyed && this.open) {
@@ -82,7 +80,7 @@ export class OuiFloatable {
82
80
  }
83
81
 
84
82
  /**
85
- * Opens the _OuiFloatable_
83
+ * Opens the floatable
86
84
  */
87
85
  show(): void {
88
86
  if (!this.#destroyed && !this.open) {
@@ -91,7 +89,7 @@ export class OuiFloatable {
91
89
  }
92
90
 
93
91
  /**
94
- * Update the position of the _OuiFloatable_, with an optional new position
92
+ * Update the position of the floatable, with an optional new position
95
93
  *
96
94
  * @param position Optional new position
97
95
  */
@@ -105,6 +103,7 @@ export class OuiFloatable {
105
103
  export type OuiFloatableOptions = {
106
104
  attribute?: string;
107
105
  position: OuiFloatablePosition;
106
+ reusable: boolean;
108
107
  };
109
108
 
110
109
  export type OuiFloatablePosition =
@@ -124,6 +123,7 @@ export type OuiFloatablePosition =
124
123
  type OuiFloatableState = {
125
124
  anchor: HTMLElement;
126
125
  content: HTMLElement;
126
+ name: string;
127
127
  options: OuiFloatableOptions;
128
128
  };
129
129
 
@@ -171,6 +171,7 @@ export function createFloatable(
171
171
  content,
172
172
  {
173
173
  position,
174
+ reusable: false,
174
175
  },
175
176
  true,
176
177
  );
@@ -194,12 +195,14 @@ function createInstance(
194
195
  throw new TypeError(MESSAGE);
195
196
  }
196
197
 
197
- let floatable = instances.get(content);
198
+ let floatable = options.reusable ? undefined : instances.get(content);
198
199
 
199
200
  if (floatable == null) {
200
201
  floatable = new OuiFloatable(getState(anchor, content, options), standalone);
201
202
 
202
- instances.set(content, floatable);
203
+ if (!options.reusable) {
204
+ instances.set(content, floatable);
205
+ }
203
206
  }
204
207
 
205
208
  return floatable;
@@ -212,7 +215,7 @@ export function getOnBeforeToggleListener(element: HTMLElement): RemovableEventL
212
215
  event => {
213
216
  const {newState, source} = event;
214
217
 
215
- if (newState === STATE_OPEN && source?.getAttribute(ARIA_DISABLED) === TRUE) {
218
+ if (newState === FLOATABLE_STATE_OPEN && source?.getAttribute(ARIA_DISABLED) === TRUE) {
216
219
  event.preventDefault();
217
220
  }
218
221
  },
@@ -255,9 +258,12 @@ function getState(
255
258
  content: HTMLElement,
256
259
  options: OuiFloatableOptions,
257
260
  ): OuiFloatableState {
261
+ const name = getAnchorName(anchor);
262
+
258
263
  const state: OuiFloatableState = {
259
264
  anchor,
260
265
  content,
266
+ name,
261
267
  options,
262
268
  };
263
269
 
@@ -266,6 +272,7 @@ function getState(
266
272
 
267
273
  export function removeFloatable(content: HTMLElement): void {
268
274
  instances.get(content)?.destroy();
275
+
269
276
  instances.delete(content);
270
277
  }
271
278
 
@@ -274,6 +281,7 @@ function setPosition(state: OuiFloatableState, override?: OuiFloatablePosition):
274
281
 
275
282
  const area = AREAS[position];
276
283
 
284
+ state.content.style.positionAnchor = state.name;
277
285
  state.content.style.positionArea = area;
278
286
 
279
287
  state.content.setAttribute(ATTRIBUTE_POSITION, POSITIONS[area]);
@@ -306,14 +314,14 @@ const ATTRIBUTE_POSITION = 'oui-position';
306
314
 
307
315
  const EVENT_BEFORE = 'beforetoggle';
308
316
 
317
+ export const FLOATABLE_STATE_OPEN = 'open';
318
+
309
319
  const MESSAGE = 'Anchor and content must be an HTMLElement or SVGElement';
310
320
 
311
321
  const POSITIONS = Object.fromEntries(
312
322
  Object.entries(AREAS).map(([position, area]) => [area, position]),
313
323
  ) as Record<string, OuiFloatablePosition>;
314
324
 
315
- const STATE_OPEN = 'open';
316
-
317
325
  const TRUE = 'true';
318
326
 
319
327
  const instances = new WeakMap<HTMLElement, OuiFloatable>();
@@ -0,0 +1,6 @@
1
+ export {
2
+ createFloatable,
3
+ type CreateOuiFloatableOptions,
4
+ type OuiFloatable,
5
+ type OuiFloatablePosition,
6
+ } from './floatable.embedded';