@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
package/src/js/tooltip.ts CHANGED
@@ -3,97 +3,145 @@ import {createElement} from '@oscarpalmer/toretto/create';
3
3
  import {on} from '@oscarpalmer/toretto/event';
4
4
  import {findAncestor} from '@oscarpalmer/toretto/find';
5
5
  import {isHTMLOrSVGElement} from '@oscarpalmer/toretto/is';
6
+ import {
7
+ createEmbeddedFloatable,
8
+ type OuiFloatable,
9
+ type OuiFloatableOptions,
10
+ } from './floatable/embedded';
6
11
  import {attributable} from './internal/attributable';
7
- import {Floatable, type FloatableOptions} from './floatable';
8
12
 
9
- class Tooltip {
10
- floatable: Floatable;
13
+ // #region Types
11
14
 
12
- timer: number | undefined;
15
+ class OuiTooltip {
16
+ readonly #state: OuiTooltipState;
13
17
 
14
- constructor(
15
- public anchor: HTMLElement,
16
- public content: HTMLElement,
17
- ) {
18
- instances.set(content, this);
19
-
20
- anchor.insertAdjacentElement('afterend', content);
18
+ /**
19
+ * Is the tooltip open?
20
+ */
21
+ get open(): boolean {
22
+ return this.#state.content.checkVisibility();
23
+ }
21
24
 
22
- this.floatable = new Floatable(anchor, content, options, false);
25
+ constructor(state: OuiTooltipState) {
26
+ this.#state = state;
23
27
 
24
- this.floatable.update();
28
+ state.floatable.update();
25
29
  }
26
30
 
27
- destroy(): void {
28
- instances.delete(this.content);
31
+ /**
32
+ * Closes the tooltip
33
+ */
34
+ hide(): void {
35
+ this.#state.floatable.hide();
36
+ }
29
37
 
30
- this.anchor = null as never;
31
- this.content = null as never;
38
+ /**
39
+ * Opens the tooltip
40
+ */
41
+ show(): void {
42
+ this.#state.floatable.show();
32
43
  }
33
44
  }
34
45
 
46
+ type OuiTooltipState = {
47
+ anchor: HTMLElement;
48
+ content: HTMLElement;
49
+ floatable: OuiFloatable;
50
+ timer?: number;
51
+ tooltip: OuiTooltip;
52
+ };
53
+
54
+ // #endregion
55
+
56
+ // #region Functions
57
+
35
58
  function activate(event: Event, focus: boolean): void {
36
59
  const anchor = findAncestor(event, SELECTOR) as HTMLElement | null;
37
60
  const content = findAncestor(event, SELECTOR_CONTENT) as HTMLElement | null;
38
61
 
39
62
  if (anchor == null && content == null) {
40
63
  if (!focused) {
41
- closeTooltip(active);
64
+ closeTooltip(activeState);
42
65
 
43
- active = undefined;
66
+ activeState = undefined;
67
+ activeTooltip = undefined;
44
68
  }
45
69
 
46
70
  return;
47
71
  }
48
72
 
49
- const instance = instances.get((anchor ?? content)!);
73
+ const state = states.get((anchor ?? content)!);
50
74
 
51
- if (instance == null || instance === active) {
75
+ if (state == null || state.tooltip === activeTooltip) {
52
76
  return;
53
77
  }
54
78
 
55
- const shouldDelay = active == null;
79
+ const shouldDelay = activeTooltip == null;
80
+
81
+ closeTooltip(activeState);
56
82
 
57
- closeTooltip(active);
83
+ activeState = state;
84
+ activeTooltip = state.tooltip;
58
85
 
59
- active = instance;
60
86
  focused = focus;
61
87
 
62
- instance.timer = setTimeout(
88
+ state.timer = setTimeout(
63
89
  () => {
64
- instance.content.showPopover({
65
- source: instance.anchor as HTMLElement,
90
+ state.content.showPopover({
91
+ source: state.anchor as HTMLElement,
66
92
  });
67
93
 
68
94
  setTimeout(() => {
69
- instance.floatable.update();
95
+ state.floatable.update();
70
96
 
71
- instance.content.setAttribute(ATTRIBUTE_CONTENT_ACTIVE, '');
97
+ state.content.setAttribute(ATTRIBUTE_CONTENT_OPEN, '');
72
98
  });
73
99
  },
74
100
  shouldDelay ? delay : 0,
75
101
  );
76
102
  }
77
103
 
78
- function closeTooltip(instance?: Tooltip): void {
79
- if (instance == null) {
104
+ function addTooltip(element: HTMLElement): void {
105
+ if (!states.has(element)) {
106
+ createTooltip(element);
107
+ }
108
+ }
109
+
110
+ function closeTooltip(state?: OuiTooltipState): void {
111
+ if (state == null) {
80
112
  return;
81
113
  }
82
114
 
83
- if (instance.timer != null) {
84
- clearTimeout(instance.timer);
115
+ if (state.timer != null) {
116
+ clearTimeout(state.timer);
85
117
  }
86
118
 
87
- instance.content?.hidePopover();
88
- instance.content?.removeAttribute(ATTRIBUTE_CONTENT_ACTIVE);
119
+ state.content?.hidePopover();
120
+ state.content?.removeAttribute(ATTRIBUTE_CONTENT_OPEN);
89
121
  }
90
122
 
91
123
  function createTooltip(anchor: HTMLElement): void {
92
124
  const content = getContent(anchor);
93
125
 
94
- if (content != null) {
95
- instances.set(anchor, new Tooltip(anchor, content));
126
+ if (content == null) {
127
+ return;
96
128
  }
129
+
130
+ anchor.insertAdjacentElement('afterend', content);
131
+
132
+ states.set(anchor, getState(anchor, content));
133
+ }
134
+
135
+ function destroyTooltip(state: OuiTooltipState): void {
136
+ clearTimeout(state.timer);
137
+
138
+ state.content.remove();
139
+ state.floatable.destroy();
140
+
141
+ state.anchor = undefined as never;
142
+ state.content = undefined as never;
143
+ state.floatable = undefined as never;
144
+ state.tooltip = undefined as never;
97
145
  }
98
146
 
99
147
  function findElements(attribute: string | null): HTMLElement[] {
@@ -169,10 +217,21 @@ function getElements(anchor: HTMLElement): HTMLElement[] | undefined {
169
217
  return [paragraph];
170
218
  }
171
219
 
172
- function onAdd(element: HTMLElement): void {
173
- if (!instances.has(element)) {
174
- createTooltip(element);
175
- }
220
+ function getState(anchor: HTMLElement, content: HTMLElement): OuiTooltipState {
221
+ const state: OuiTooltipState = {
222
+ anchor,
223
+ content,
224
+ floatable: createEmbeddedFloatable(anchor, content, options),
225
+ tooltip: null as never,
226
+ };
227
+
228
+ state.tooltip = new OuiTooltip(state);
229
+
230
+ return state;
231
+ }
232
+
233
+ function getTooltip(element: HTMLElement): OuiTooltip | undefined {
234
+ return states.get(element)?.tooltip;
176
235
  }
177
236
 
178
237
  function onFocusin(event: Event): void {
@@ -180,7 +239,7 @@ function onFocusin(event: Event): void {
180
239
  }
181
240
 
182
241
  function onFocusout(): void {
183
- if (active == null) {
242
+ if (activeTooltip == null) {
184
243
  return;
185
244
  }
186
245
 
@@ -190,16 +249,17 @@ function onFocusout(): void {
190
249
 
191
250
  focusTimer = setTimeout(() => {
192
251
  if (
193
- active == null ||
252
+ activeTooltip == null ||
194
253
  document.activeElement == null ||
195
254
  findAncestor(document.activeElement, SELECTOR) != null
196
255
  ) {
197
256
  return;
198
257
  }
199
258
 
200
- closeTooltip(active);
259
+ closeTooltip(activeState);
201
260
 
202
- active = undefined;
261
+ activeState = undefined;
262
+ activeTooltip = undefined;
203
263
  });
204
264
  }
205
265
 
@@ -207,10 +267,16 @@ function onPointermove(event: Event): void {
207
267
  activate(event, false);
208
268
  }
209
269
 
210
- function onRemove(element: HTMLElement): void {
211
- instances.get(element)?.destroy();
270
+ function removeTooltip(element: HTMLElement): void {
271
+ const state = states.get(element);
272
+
273
+ if (state == null) {
274
+ return;
275
+ }
276
+
277
+ destroyTooltip(state);
212
278
 
213
- instances.delete(element);
279
+ states.delete(element);
214
280
  }
215
281
 
216
282
  /**
@@ -219,12 +285,16 @@ function onRemove(element: HTMLElement): void {
219
285
  * _(Default delay is half a second, or 500 milliseconds)_
220
286
  * @param value Delay in milliseconds
221
287
  */
222
- export function setTooltipDelay(value: number): void {
288
+ function setTooltipDelay(value: number): void {
223
289
  if (typeof value === 'number' && !Number.isNaN(value) && value >= 0) {
224
290
  delay = value;
225
291
  }
226
292
  }
227
293
 
294
+ // #endregion
295
+
296
+ // #region Variables
297
+
228
298
  const ARIA_DESCRIBEDBY = 'aria-describedby';
229
299
 
230
300
  const ARIA_DESCRIPTION = 'aria-description';
@@ -241,15 +311,15 @@ const ATTRIBUTE = 'oui-tooltip';
241
311
 
242
312
  const ATTRIBUTE_CONTENT = `${ATTRIBUTE}-content`;
243
313
 
244
- const ATTRIBUTE_CONTENT_ACTIVE = `${ATTRIBUTE_CONTENT}-active`;
314
+ const ATTRIBUTE_CONTENT_OPEN = `${ATTRIBUTE_CONTENT}-open`;
245
315
 
246
316
  const SELECTOR = `[${ATTRIBUTE}]`;
247
317
 
248
318
  const SELECTOR_CONTENT = `[${ATTRIBUTE_CONTENT}]`;
249
319
 
250
- const instances = new WeakMap<HTMLElement, Tooltip>();
320
+ const states = new WeakMap<HTMLElement, OuiTooltipState>();
251
321
 
252
- const options: FloatableOptions = {
322
+ const options: OuiFloatableOptions = {
253
323
  attribute: `${ATTRIBUTE}-position`,
254
324
  position: 'above',
255
325
  };
@@ -258,12 +328,26 @@ let delay = 500;
258
328
 
259
329
  let focused = false;
260
330
 
261
- let active: Tooltip | undefined;
331
+ let activeState: OuiTooltipState | undefined;
332
+
333
+ let activeTooltip: OuiTooltip | undefined;
262
334
 
263
335
  let focusTimer: number | undefined;
264
336
 
337
+ // #endregion
338
+
339
+ // #region Initialization
340
+
265
341
  on(document, 'focusin', onFocusin);
266
342
  on(document, 'focusout', onFocusout);
267
343
  on(document, 'pointermove', onPointermove);
268
344
 
269
- attributable(ATTRIBUTE, onAdd, onRemove);
345
+ attributable(ATTRIBUTE, addTooltip, removeTooltip);
346
+
347
+ // #endregion
348
+
349
+ // #region Exports
350
+
351
+ export {getTooltip, setTooltipDelay, type OuiTooltip};
352
+
353
+ // #endregion
@@ -1,30 +0,0 @@
1
-
2
- :where([oui-popover-content]) {
3
- box-shadow: inset 0 1px 0 0 transparent, 0 0 0 1px rgba(0, 0, 0, 0.05), 0 1px 2px -0.5px rgba(0, 0, 0, 0.05), 0 2px 4px -1px rgba(0, 0, 0, 0.05), 0 4px 8px -2px rgba(0, 0, 0, 0.05), 0 8px 16px -4px rgba(0, 0, 0, 0.05), 0 16px 32px -8px rgba(0, 0, 0, 0.05), 0 32px 64px -16px rgba(0, 0, 0, 0.05), 0 64px 128px -32px rgba(0, 0, 0, 0.05);
4
- --margin-bottom: 0.5em;
5
- --margin-left: 0.5em;
6
- --margin-right: 0.5em;
7
- --margin-top: 0.5em;
8
- inset-block: var(--margin-top) var(--margin-bottom);
9
- inset-inline: var(--margin-left) var(--margin-right);
10
- }
11
- :where([oui-popover-content])[oui-position$=bottom] {
12
- --margin-bottom: 0;
13
- }
14
- :where([oui-popover-content])[oui-position$=end] {
15
- --margin-right: 0;
16
- }
17
- :where([oui-popover-content])[oui-position$=start] {
18
- --margin-left: 0;
19
- }
20
- :where([oui-popover-content])[oui-position$=top] {
21
- --margin-top: 0;
22
- }
23
- :where([oui-popover-content]) {
24
- padding: 1rem;
25
- background-color: var(--oui-absolute);
26
- border: none;
27
- border-radius: 0.25rem;
28
- }
29
-
30
-
@@ -1,59 +0,0 @@
1
- //#region src/js/floatable.d.ts
2
- declare class Floatable {
3
- #private;
4
- anchor: HTMLElement;
5
- content: HTMLElement;
6
- options: FloatableOptions;
7
- /**
8
- * Is the _Floatable_ open?
9
- */
10
- get open(): boolean;
11
- constructor(anchor: HTMLElement, content: HTMLElement, options: FloatableOptions, standalone: boolean);
12
- /**
13
- * Destroy the floatable, removing it from the DOM and cleaning up references
14
- */
15
- destroy(): void;
16
- /**
17
- * Hide the floatable
18
- */
19
- hide(): void;
20
- /**
21
- * Show the floatable
22
- */
23
- show(): void;
24
- /**
25
- * Update the position of the floatable, with an optional new position
26
- *
27
- * @param position Optional new position
28
- */
29
- update(position?: FloatablePosition): void;
30
- }
31
- type FloatableOptions = {
32
- attribute?: string;
33
- position: FloatablePosition;
34
- };
35
- type FloatablePosition = 'above' | 'above-end' | 'above-start' | 'below' | 'below-end' | 'below-start' | 'end' | 'end-bottom' | 'end-top' | 'start' | 'start-bottom' | 'start-top';
36
- type GetFloatableOptions = {
37
- /**
38
- * Open the _Floatable_ when created? _(defaults to `true`)_
39
- */
40
- open?: boolean;
41
- /**
42
- * Position of the _Floatable_ _(defaults to `below`)_
43
- */
44
- position?: FloatablePosition;
45
- };
46
- declare function createFloatable(anchor: HTMLElement, content: HTMLElement, options: FloatableOptions, standalone: boolean): Floatable;
47
- /**
48
- * Create a _Floatable_ for an anchor and content element
49
- *
50
- * @param anchor Anchor element
51
- * @param content Content element
52
- * @param options _Floatable_ options
53
- * @returns _Floatable_ instance
54
- */
55
- declare function getFloatable(anchor: HTMLElement | SVGElement, content: HTMLElement | SVGElement, options?: GetFloatableOptions): Floatable;
56
- declare function removeFloatable(content: HTMLElement): void;
57
- declare const ATTRIBUTE_FLOATABLE = "oui-floatable";
58
- //#endregion
59
- export { ATTRIBUTE_FLOATABLE, Floatable, FloatableOptions, FloatablePosition, createFloatable, getFloatable, removeFloatable };
@@ -1,130 +0,0 @@
1
- import { isPlainObject } from "@oscarpalmer/atoms/is";
2
- import { isHTMLOrSVGElement } from "@oscarpalmer/toretto/is";
3
- //#region src/js/floatable.ts
4
- var Floatable = class {
5
- anchor;
6
- content;
7
- options;
8
- #destroyed = false;
9
- /**
10
- * Is the _Floatable_ open?
11
- */
12
- get open() {
13
- return this.content.checkVisibility();
14
- }
15
- constructor(anchor, content, options, standalone) {
16
- this.anchor = anchor;
17
- this.content = content;
18
- this.options = options;
19
- if (standalone) {
20
- content.setAttribute(ATTRIBUTE_FLOATABLE, "");
21
- content.popover = "manual";
22
- }
23
- const anchorName = getAnchorName(anchor);
24
- anchor.style.anchorName = anchorName;
25
- content.style.positionAnchor = anchorName;
26
- }
27
- /**
28
- * Destroy the floatable, removing it from the DOM and cleaning up references
29
- */
30
- destroy() {
31
- if (this.#destroyed) return;
32
- this.#destroyed = true;
33
- removeFloatable(this.content);
34
- this.anchor = null;
35
- this.content = null;
36
- }
37
- /**
38
- * Hide the floatable
39
- */
40
- hide() {
41
- if (!this.#destroyed) this.content.hidePopover();
42
- }
43
- /**
44
- * Show the floatable
45
- */
46
- show() {
47
- if (!this.#destroyed) this.content.showPopover();
48
- }
49
- /**
50
- * Update the position of the floatable, with an optional new position
51
- *
52
- * @param position Optional new position
53
- */
54
- update(position) {
55
- if (!this.#destroyed) setPosition(this, position);
56
- }
57
- };
58
- function createFloatable(anchor, content, options, standalone) {
59
- if (!isHTMLOrSVGElement(anchor) || !isHTMLOrSVGElement(content)) throw new TypeError("Anchor and content must be an HTMLElement or SVGElement.");
60
- let floatable = instances.get(content);
61
- if (floatable == null) {
62
- floatable = new Floatable(anchor, content, options, standalone);
63
- instances.set(content, floatable);
64
- }
65
- return floatable;
66
- }
67
- function getAnchorName(anchor) {
68
- let { anchorName } = anchor.style;
69
- if (anchorName.length > 0) return anchorName;
70
- index += 1;
71
- return `--oui-floatable-anchor-${index}`;
72
- }
73
- /**
74
- * Create a _Floatable_ for an anchor and content element
75
- *
76
- * @param anchor Anchor element
77
- * @param content Content element
78
- * @param options _Floatable_ options
79
- * @returns _Floatable_ instance
80
- */
81
- function getFloatable(anchor, content, options) {
82
- const { open, position } = getOptions(options);
83
- const floatable = createFloatable(anchor, content, { position }, true);
84
- floatable?.update(position);
85
- if (open) floatable?.show();
86
- return floatable;
87
- }
88
- function getOptions(input) {
89
- const object = isPlainObject(input) ? input : {};
90
- return {
91
- open: typeof object.open === "boolean" ? object.open : true,
92
- position: object.position != null && object.position in AREAS ? object.position : "below"
93
- };
94
- }
95
- function getPosition(floatable, override) {
96
- if (override != null && override in AREAS) return override;
97
- if (floatable.options.attribute == null) return floatable.options.position;
98
- const attribute = floatable.anchor.getAttribute(floatable.options.attribute) ?? "";
99
- return attribute in AREAS ? attribute : floatable.options.position;
100
- }
101
- function removeFloatable(content) {
102
- instances.get(content)?.destroy();
103
- instances.delete(content);
104
- }
105
- function setPosition(floatable, override) {
106
- const area = AREAS[getPosition(floatable, override)];
107
- floatable.content.style.positionArea = area;
108
- floatable.content.setAttribute(ATTRIBUTE_POSITION, POSITIONS[area]);
109
- }
110
- const AREAS = {
111
- above: "block-start",
112
- "above-end": "start span-start",
113
- "above-start": "start span-end",
114
- below: "block-end",
115
- "below-end": "end span-start",
116
- "below-start": "end span-end",
117
- end: "center end",
118
- "end-bottom": "span-start end",
119
- "end-top": "span-end end",
120
- start: "center start",
121
- "start-bottom": "span-start start",
122
- "start-top": "span-end start"
123
- };
124
- const ATTRIBUTE_FLOATABLE = "oui-floatable";
125
- const ATTRIBUTE_POSITION = "oui-position";
126
- const POSITIONS = Object.fromEntries(Object.entries(AREAS).map(([position, area]) => [area, position]));
127
- const instances = /* @__PURE__ */ new WeakMap();
128
- let index = 0;
129
- //#endregion
130
- export { ATTRIBUTE_FLOATABLE, Floatable, createFloatable, getFloatable, removeFloatable };
@@ -1,10 +0,0 @@
1
- @use 'mixins.scss' as mixins;
2
-
3
- :where([oui-popover-content]) {
4
- @include mixins.floatable;
5
-
6
- padding: 1rem;
7
- background-color: var(--oui-absolute);
8
- border: none;
9
- border-radius: 0.25rem;
10
- }