@oscarpalmer/oui 0.17.0 → 0.19.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 (72) hide show
  1. package/dist/css/badge.css +13 -13
  2. package/dist/css/button.css +9 -9
  3. package/dist/css/color.css +99 -99
  4. package/dist/css/content.css +10 -12
  5. package/dist/css/flex.css +39 -39
  6. package/dist/css/form.css +47 -60
  7. package/dist/css/message.css +10 -6
  8. package/dist/css/reset.css +178 -57
  9. package/dist/css/table.css +4 -4
  10. package/dist/css/tooltip.css +26 -1
  11. package/dist/js/attributable.full.js +36 -0
  12. package/dist/js/attributable.js +30 -45
  13. package/dist/js/floatable.full.js +398 -0
  14. package/dist/js/floatable.js +254 -169
  15. package/dist/js/focus-trap/embedded.full.js +408 -0
  16. package/dist/js/focus-trap/embedded.js +131 -131
  17. package/dist/js/focus-trap/standalone.full.js +447 -0
  18. package/dist/js/focus-trap/standalone.js +4 -4
  19. package/dist/js/index.js +3 -4
  20. package/dist/js/oui.full.js +1573 -0
  21. package/dist/js/popover.full.js +760 -0
  22. package/dist/js/popover.js +84 -163
  23. package/dist/js/splitter.full.js +769 -0
  24. package/dist/js/splitter.js +264 -246
  25. package/dist/js/tooltip.full.js +613 -0
  26. package/dist/js/tooltip.js +160 -151
  27. package/package.json +35 -63
  28. package/src/css/a11y.scss +1 -1
  29. package/src/css/badge.scss +23 -19
  30. package/src/css/button.scss +9 -9
  31. package/src/css/color.scss +99 -99
  32. package/src/css/content.scss +6 -9
  33. package/src/css/flex.scss +21 -20
  34. package/src/css/form.scss +47 -62
  35. package/src/css/message.scss +11 -12
  36. package/src/css/mixins.scss +16 -10
  37. package/src/css/popover.scss +2 -2
  38. package/src/css/reset.scss +181 -55
  39. package/src/css/splitter.scss +15 -13
  40. package/src/css/table.scss +11 -11
  41. package/src/css/tooltip.scss +31 -2
  42. package/src/js/attributable.ts +3 -10
  43. package/src/js/floatable.ts +370 -141
  44. package/src/js/focus-trap/embedded.ts +141 -74
  45. package/src/js/focus-trap/standalone.ts +4 -4
  46. package/src/js/index.ts +1 -0
  47. package/src/js/popover.ts +52 -122
  48. package/src/js/splitter.ts +248 -131
  49. package/src/js/tooltip.ts +143 -68
  50. package/types/floatable.d.ts +17 -10
  51. package/types/focus-trap/embedded.d.ts +18 -5
  52. package/types/index.d.ts +1 -0
  53. package/types/popover.d.ts +2 -0
  54. package/types/splitter.d.ts +13 -1
  55. package/types/tooltip.d.ts +2 -1
  56. package/dist/css/value.css +0 -1
  57. package/dist/js/attributable.cjs +0 -51
  58. package/dist/js/floatable.cjs +0 -176
  59. package/dist/js/focus-trap/embedded.cjs +0 -150
  60. package/dist/js/focus-trap/standalone.cjs +0 -8
  61. package/dist/js/index.cjs +0 -6
  62. package/dist/js/popover.cjs +0 -166
  63. package/dist/js/splitter.cjs +0 -272
  64. package/dist/js/tooltip.cjs +0 -166
  65. package/types/attributable.d.cts +0 -5
  66. package/types/floatable.d.cts +0 -20
  67. package/types/focus-trap/embedded.d.cts +0 -15
  68. package/types/focus-trap/standalone.d.cts +0 -3
  69. package/types/index.d.cts +0 -14
  70. package/types/popover.d.cts +0 -14
  71. package/types/splitter.d.cts +0 -10
  72. package/types/tooltip.d.cts +0 -3
@@ -4,57 +4,119 @@ import {findAncestor} from '@oscarpalmer/toretto/find';
4
4
  import {getFocusable} from '@oscarpalmer/toretto/focusable';
5
5
 
6
6
  export class FocusTrap {
7
- active = true;
7
+ disabled = false;
8
+
9
+ connected = true;
10
+
11
+ element: HTMLElement;
12
+
13
+ embedded: boolean;
14
+
15
+ get contain(): boolean {
16
+ return this.element.hasAttribute(FOCUS_TRAP_CONTAIN);
17
+ }
18
+
19
+ get noescape(): boolean {
20
+ return this.element.hasAttribute(FOCUS_TRAP_NOESCAPE);
21
+ }
22
+
23
+ constructor(element: HTMLElement, options?: Options) {
24
+ this.element = element;
25
+ this.embedded = options != null;
8
26
 
9
- constructor(public element: HTMLElement) {
10
- element.setAttribute('aria-modal', 'true');
11
27
  element.setAttribute('tabindex', '-1');
28
+
29
+ if (options != null) {
30
+ element.setAttribute(FOCUS_TRAP_SELECTOR, '');
31
+ }
32
+
33
+ if (options?.noescape) {
34
+ element.setAttribute(FOCUS_TRAP_NOESCAPE, '');
35
+ }
36
+ }
37
+
38
+ connect(): void {
39
+ setConnected(this, true);
12
40
  }
13
41
 
14
- destroy() {
42
+ destroy(): void {
15
43
  this.element = undefined as never;
16
44
  }
17
45
 
18
- disable() {
19
- if (this.active && !this.element.hasAttribute(`${selector}-noescape`)) {
46
+ disable(): void {
47
+ if (!(this.disabled || this.noescape)) {
20
48
  setDisabled(this, true);
21
49
  }
22
50
  }
23
51
 
24
- enable() {
25
- if (!this.active) {
52
+ disconnect(): void {
53
+ setConnected(this, false);
54
+ }
55
+
56
+ enable(): void {
57
+ if (this.disabled) {
26
58
  setDisabled(this, false);
27
59
  }
28
60
  }
61
+
62
+ focus(last: boolean): void {
63
+ tabToTrap(this, getFocusable(this.element), this.element, last ? -1 : 0);
64
+ }
29
65
  }
30
66
 
67
+ type Options = {
68
+ noescape: boolean;
69
+ };
70
+
31
71
  //
32
72
 
33
- export function createFocusTrap(element: HTMLElement): FocusTrap {
34
- let focusTrap = focusTraps.get(element);
73
+ export function createFocusTrap(element: HTMLElement, options?: Options): FocusTrap {
74
+ let focusTrap = FOCUSTRAPS_ALL.get(element);
35
75
 
36
76
  if (focusTrap == null) {
37
- focusTrap = new FocusTrap(element);
77
+ focusTrap = new FocusTrap(element, options);
38
78
 
39
- focusTraps.set(element, focusTrap);
79
+ FOCUSTRAPS_ALL.set(element, focusTrap);
40
80
  }
41
81
 
42
82
  return focusTrap;
43
83
  }
44
84
 
45
85
  function onEscape(event: KeyboardEvent): void {
46
- focusTraps
47
- .get(
48
- findAncestor(event.target as HTMLElement, `[${selector}]`) as HTMLElement,
49
- )
50
- ?.disable();
86
+ let element = findAncestor(event.target as HTMLElement, ATTRIBUTE_SELECTOR) as HTMLElement;
87
+
88
+ let focusTrap = FOCUSTRAPS_ALL.get(element);
89
+
90
+ if (element == null || focusTrap == null) {
91
+ return;
92
+ }
93
+
94
+ while (focusTrap.disabled) {
95
+ element = findAncestor(
96
+ focusTrap.element.parentElement as HTMLElement,
97
+ ATTRIBUTE_SELECTOR,
98
+ ) as HTMLElement;
99
+
100
+ focusTrap = FOCUSTRAPS_ALL.get(element);
101
+
102
+ if (element == null || focusTrap == null) {
103
+ return;
104
+ }
105
+
106
+ if (!focusTrap.disabled) {
107
+ break;
108
+ }
109
+ }
110
+
111
+ focusTrap?.disable();
51
112
  }
52
113
 
53
114
  function onFocusIn(event: Event): void {
54
- lastTarget =
55
- findAncestor(event.target as HTMLElement, `[${selector}]`) == null
56
- ? undefined
57
- : (event.target as HTMLElement);
115
+ const focusTrap = FOCUSTRAPS_ALL.get(
116
+ findAncestor(event.target as HTMLElement, ATTRIBUTE_SELECTOR) as HTMLElement,
117
+ );
118
+
119
+ lastTarget = focusTrap == null ? undefined : (event.target as HTMLElement);
58
120
  }
59
121
 
60
122
  function onKeydown(event: KeyboardEvent): void {
@@ -73,47 +135,42 @@ function onKeydown(event: KeyboardEvent): void {
73
135
  }
74
136
 
75
137
  function onPointerdown(event: MouseEvent | TouchEvent): void {
76
- const lastFocusTrap = findAncestor(
77
- lastTarget as HTMLElement,
78
- `[${selector}]`,
79
- ) as HTMLElement;
138
+ const lastFocusTrap = findAncestor(lastTarget as HTMLElement, ATTRIBUTE_SELECTOR) as HTMLElement;
80
139
 
81
140
  const nextFocusTrap = findAncestor(
82
141
  event.target as HTMLElement,
83
- `[${selector}]`,
142
+ ATTRIBUTE_SELECTOR,
84
143
  ) as HTMLElement;
85
144
 
86
145
  if (lastFocusTrap != null && nextFocusTrap !== lastFocusTrap) {
87
- event.preventDefault();
146
+ if (FOCUSTRAPS_ALL.get(lastFocusTrap)?.contain ?? false) {
147
+ event.preventDefault();
88
148
 
89
- lastTarget?.focus();
149
+ lastTarget?.focus();
150
+ }
90
151
  }
91
152
 
92
- if (disabled.size === 0) {
153
+ if (FOCUSTRAPS_DISABLED.size === 0) {
93
154
  return;
94
155
  }
95
156
 
96
- for (const focusTrap of disabled) {
157
+ for (const focusTrap of FOCUSTRAPS_DISABLED) {
97
158
  if (focusTrap.element !== nextFocusTrap) {
98
159
  focusTrap.enable();
99
- return;
100
160
  }
101
161
  }
102
162
  }
103
163
 
104
164
  function onTab(event: KeyboardEvent): void {
105
- const element = findAncestor(
106
- event.target as HTMLElement,
107
- `[${selector}]`,
108
- ) as HTMLElement;
165
+ const element = findAncestor(event.target as HTMLElement, ATTRIBUTE_SELECTOR) as HTMLElement;
109
166
 
110
- const focusTrap = focusTraps.get(element);
167
+ const focusTrap = FOCUSTRAPS_ALL.get(element);
111
168
 
112
169
  if (focusTrap == null) {
113
170
  return;
114
171
  }
115
172
 
116
- if (focusTrap.active) {
173
+ if (!focusTrap.disabled) {
117
174
  event.preventDefault();
118
175
  }
119
176
 
@@ -124,40 +181,39 @@ function onTab(event: KeyboardEvent): void {
124
181
  if (index === -1 || elements.length === 0) {
125
182
  tabToTrap(focusTrap, elements, element);
126
183
  } else {
127
- tabToElement(event, focusTrap, elements, index);
184
+ tabToElement(focusTrap, elements, index, event.shiftKey ? -1 : 1);
128
185
  }
129
186
  }
130
187
 
131
- function setDisabled(focusTrap: FocusTrap, value: boolean): void {
132
- focusTrap.element.setAttribute('aria-modal', String(!value));
188
+ function setConnected(focusTrap: FocusTrap, value: boolean): void {
189
+ if (!focusTrap.embedded || focusTrap.connected === value) {
190
+ return;
191
+ }
192
+
193
+ focusTrap.connected = value;
194
+ }
133
195
 
134
- focusTrap.active = !value;
196
+ function setDisabled(focusTrap: FocusTrap, value: boolean): void {
197
+ focusTrap.disabled = value;
135
198
 
136
199
  if (value) {
137
- disabled.add(focusTrap);
200
+ FOCUSTRAPS_DISABLED.add(focusTrap);
138
201
  } else {
139
- disabled.delete(focusTrap);
202
+ FOCUSTRAPS_DISABLED.delete(focusTrap);
140
203
  }
141
204
  }
142
205
 
143
206
  function tabToElement(
144
- event: KeyboardEvent,
145
207
  focusTrap: FocusTrap,
146
208
  elements: Element[],
147
209
  index: number,
210
+ modifier: number,
148
211
  ): void {
149
- const next = clamp(
150
- index + (event.shiftKey ? -1 : 1),
151
- 0,
152
- elements.length - 1,
153
- true,
154
- );
212
+ const {length} = elements;
213
+ const next = clamp(index + modifier, 0, length - 1, true);
155
214
 
156
- if (!focusTrap.active) {
157
- if (
158
- (index === length - 1 && next === 0) ||
159
- (index === 0 && next === length - 1)
160
- ) {
215
+ if (focusTrap.disabled) {
216
+ if ((index === length - 1 && next === 0) || (index === 0 && next === length - 1)) {
161
217
  lastTarget = undefined;
162
218
 
163
219
  focusTrap.enable();
@@ -166,50 +222,61 @@ function tabToElement(
166
222
  return;
167
223
  }
168
224
 
169
- (elements[next] as HTMLElement).focus();
225
+ const element = elements[next] as HTMLElement;
226
+ const elementFocusTrap = FOCUSTRAPS_ALL.get(element);
170
227
 
171
- lastTarget = elements[next] as HTMLElement;
228
+ if (elementFocusTrap != null) {
229
+ elementFocusTrap.focus(modifier === -1);
230
+ } else {
231
+ (elements[next] as HTMLElement).focus();
232
+
233
+ lastTarget = elements[next] as HTMLElement;
234
+ }
172
235
  }
173
236
 
174
237
  function tabToTrap(
175
238
  focusTrap: FocusTrap,
176
239
  elements: Element[],
177
240
  element: HTMLElement,
241
+ index?: number,
178
242
  ): void {
179
- if (focusTrap.active) {
180
- const next = length === 0 ? element : elements[0];
243
+ if (focusTrap.disabled) {
244
+ lastTarget = undefined;
245
+
246
+ focusTrap.enable();
247
+ } else {
248
+ const next = elements.length === 0 ? element : elements.at(index ?? 0);
181
249
 
182
250
  (next as HTMLElement).focus();
183
251
 
184
252
  lastTarget = next as HTMLElement;
185
- } else {
186
- lastTarget = undefined;
187
-
188
- focusTrap.enable();
189
253
  }
190
254
  }
191
255
 
192
256
  //
193
257
 
194
- const disabled = new Set<FocusTrap>();
258
+ export const FOCUS_TRAP_SELECTOR = 'oui-focus-trap';
195
259
 
196
- const eventOptions = {
260
+ const ATTRIBUTE_SELECTOR = `[${FOCUS_TRAP_SELECTOR}]`;
261
+
262
+ const EVENT_OPTIONS: AddEventListenerOptions = {
197
263
  capture: true,
198
264
  passive: false,
199
265
  };
200
266
 
201
- export const focusTraps = new WeakMap<HTMLElement, FocusTrap>();
202
-
203
- let lastTarget: HTMLElement | undefined;
267
+ export const FOCUS_TRAP_CONTAIN = `${FOCUS_TRAP_SELECTOR}-contain`;
204
268
 
205
- export const selector = 'oui-focus-trap';
269
+ export const FOCUS_TRAP_NOESCAPE = `${FOCUS_TRAP_SELECTOR}-noescape`;
206
270
 
207
- //
271
+ export const FOCUSTRAPS_ALL: WeakMap<HTMLElement, FocusTrap> = new WeakMap();
208
272
 
209
- on(document, 'focusin', onFocusIn, eventOptions);
273
+ const FOCUSTRAPS_DISABLED: Set<FocusTrap> = new Set();
210
274
 
211
- on(document, 'keydown', onKeydown, eventOptions);
275
+ let lastTarget: HTMLElement | undefined;
212
276
 
213
- on(document, 'mousedown', onPointerdown, eventOptions);
277
+ //
214
278
 
215
- on(document, 'touchstart', onPointerdown, eventOptions);
279
+ on(document, 'focusin', onFocusIn, EVENT_OPTIONS);
280
+ on(document, 'keydown', onKeydown, EVENT_OPTIONS);
281
+ on(document, 'mousedown', onPointerdown, EVENT_OPTIONS);
282
+ on(document, 'touchstart', onPointerdown, EVENT_OPTIONS);
@@ -1,9 +1,9 @@
1
1
  import {attributable} from '../attributable';
2
- import {createFocusTrap, focusTraps, selector} from './embedded';
2
+ import {createFocusTrap, FOCUS_TRAP_SELECTOR, FOCUSTRAPS_ALL} from './embedded';
3
3
 
4
4
  function removeFocusTraps(element: HTMLElement): void {
5
- focusTraps.get(element)?.destroy();
6
- focusTraps.delete(element);
5
+ FOCUSTRAPS_ALL.get(element)?.destroy();
6
+ FOCUSTRAPS_ALL.delete(element);
7
7
  }
8
8
 
9
- attributable(selector, createFocusTrap, removeFocusTraps);
9
+ attributable(FOCUS_TRAP_SELECTOR, createFocusTrap, removeFocusTraps);
package/src/js/index.ts CHANGED
@@ -1,3 +1,4 @@
1
1
  export * from './focus-trap/standalone';
2
2
  export * from './popover';
3
+ export * from './splitter';
3
4
  export * from './tooltip';
package/src/js/popover.ts CHANGED
@@ -1,29 +1,26 @@
1
1
  import {isNullableOrWhitespace} from '@oscarpalmer/atoms/is';
2
- import {on} from '@oscarpalmer/toretto/event';
3
- import {findAncestor} from '@oscarpalmer/toretto/find';
4
2
  import {getFocusable} from '@oscarpalmer/toretto/focusable';
5
- import {activate, deactivate, Floatable} from './floatable';
3
+ import {Floatable} from './floatable';
6
4
  import {createFocusTrap, type FocusTrap} from './focus-trap/embedded';
7
5
 
8
6
  declare global {
7
+ // biome-ignore lint/nursery/useConsistentTypeDefinitions: Extending builtins to allow custom element type help
9
8
  interface HTMLElementTagNameMap {
10
9
  'oui-popover': OuiPopoverElement;
11
10
  }
12
11
  }
13
12
 
14
- const mapped = new Map<HTMLElement, Floatable>();
15
-
16
13
  let index = 0;
17
14
 
18
15
  export class OuiPopoverElement extends HTMLElement {
19
- #floatable: Floatable;
20
- #focusTrap: FocusTrap;
16
+ readonly #floatable: Floatable;
17
+ readonly #focusTrap: FocusTrap;
21
18
 
22
19
  constructor() {
23
20
  super();
24
21
 
25
- const content = this.querySelector(':scope > [oui-popover-content]');
26
- const toggle = this.querySelector(':scope > [oui-popover-toggle]');
22
+ const content = this.querySelector(SELECTOR_CONTENT);
23
+ const toggle = this.querySelector(SELECTOR_TOGGLE);
27
24
 
28
25
  if (!(content instanceof HTMLElement)) {
29
26
  throw new Error(
@@ -40,40 +37,56 @@ export class OuiPopoverElement extends HTMLElement {
40
37
  content.hidden = true;
41
38
 
42
39
  content.setAttribute('role', 'dialog');
43
- content.setAttribute('oui-focus-trap', '');
44
- content.setAttribute('oui-focus-trap-noescape', '');
40
+ content.setAttribute('aria-modal', 'true');
45
41
 
46
42
  if (isNullableOrWhitespace(content.id)) {
47
- content.setAttribute('id', `oui-popover-content-${++index}`);
43
+ content.setAttribute('id', `${ATTRIBUTE_ID_PREFIX}_${++index}`);
48
44
  }
49
45
 
50
46
  toggle.setAttribute('aria-controls', content.id);
51
47
  toggle.setAttribute('aria-haspopup', 'dialog');
52
48
 
53
- this.#floatable = new Floatable(
54
- toggle,
49
+ this.#floatable = new Floatable({
55
50
  content,
56
- 'position',
57
- 'below-start',
58
- false,
59
- );
60
-
61
- this.#focusTrap = createFocusTrap(content);
62
-
63
- mapped.set(toggle, this.#floatable);
64
- mapped.set(content, this.#floatable);
51
+ anchor: toggle,
52
+ defaultPosition: 'below-start',
53
+ interactive: true,
54
+ positionAttribute: 'position',
55
+ preferAbove: false,
56
+ onAfter: (active: boolean): void => {
57
+ if (active) {
58
+ focus(content);
59
+ } else if (!this.#floatable.ignoreFocus) {
60
+ toggle.focus();
61
+ }
62
+ },
63
+ });
64
+
65
+ this.#focusTrap = createFocusTrap(content, {
66
+ noescape: true,
67
+ });
68
+
69
+ POPOVERS.set(toggle, this.#floatable);
70
+ POPOVERS.set(content, this.#floatable);
65
71
  }
66
72
 
67
73
  close(): void {
68
74
  this.hidePopover();
69
75
  }
70
76
 
71
- hidePopover(): void {
72
- if (this.#floatable.active) {
73
- closeAbove(this.#floatable.content);
74
- deactivate(this.#floatable, active, order);
77
+ connectedCallback(): void {
78
+ this.#floatable.enable();
79
+ this.#focusTrap.connect();
80
+ }
75
81
 
76
- this.#floatable.anchor.focus();
82
+ disconnectedCallback(): void {
83
+ this.#floatable.disable();
84
+ this.#focusTrap.disconnect();
85
+ }
86
+
87
+ override hidePopover(): void {
88
+ if (this.#floatable.active && this.isConnected) {
89
+ this.#floatable.toggle(false);
77
90
  }
78
91
  }
79
92
 
@@ -81,9 +94,9 @@ export class OuiPopoverElement extends HTMLElement {
81
94
  this.showPopover();
82
95
  }
83
96
 
84
- showPopover(): void {
85
- if (!this.#floatable.active) {
86
- toggle(this.#floatable.anchor);
97
+ override showPopover(): void {
98
+ if (!this.#floatable.active && this.isConnected) {
99
+ this.#floatable.toggle(true);
87
100
  }
88
101
  }
89
102
 
@@ -91,7 +104,7 @@ export class OuiPopoverElement extends HTMLElement {
91
104
  this.togglePopover();
92
105
  }
93
106
 
94
- togglePopover(options?: boolean): boolean {
107
+ override togglePopover(options?: boolean): boolean {
95
108
  if (typeof options === 'boolean') {
96
109
  if (options === true) {
97
110
  this.showPopover();
@@ -116,32 +129,6 @@ export class OuiPopoverElement extends HTMLElement {
116
129
 
117
130
  //
118
131
 
119
- function closeAll(): void {
120
- for (const floatable of active) {
121
- deactivate(floatable, active, order);
122
- }
123
- }
124
-
125
- function closeAbove(element: HTMLElement): void {
126
- const index = order.findIndex(
127
- floatable => floatable.anchor === element || floatable.content === element,
128
- );
129
-
130
- if (index === -1 || index === order.length - 1) {
131
- return;
132
- }
133
-
134
- while (index < order.length - 1) {
135
- const floatable = order.pop();
136
-
137
- if (floatable != null) {
138
- deactivate(floatable, active, order);
139
- }
140
- }
141
-
142
- focus(order[index].content);
143
- }
144
-
145
132
  function focus(content: HTMLElement): void {
146
133
  let target = getFocusable(content)[0];
147
134
 
@@ -154,75 +141,18 @@ function focus(content: HTMLElement): void {
154
141
  });
155
142
  }
156
143
 
157
- function onClick(event: MouseEvent): void {
158
- const related = findAncestor(
159
- event.target as never,
160
- '[oui-popover-toggle], [oui-popover-content]',
161
- );
162
-
163
- if (!(related instanceof HTMLElement)) {
164
- closeAll();
165
- } else if (related.hasAttribute('oui-popover-content')) {
166
- closeAbove(related);
167
- } else {
168
- toggle(related);
169
- }
170
- }
171
-
172
- function onKeydown(event: KeyboardEvent): void {
173
- if (event.key !== 'Escape' || order.length === 0) {
174
- return;
175
- }
176
-
177
- const related = findAncestor(event.target as never, '[oui-popover-content]');
178
-
179
- if (!(related instanceof HTMLElement)) {
180
- return;
181
- }
182
-
183
- closeAbove(related);
184
-
185
- const floatable = mapped.get(related);
186
-
187
- if (floatable != null) {
188
- deactivate(floatable, active, order);
189
-
190
- floatable.anchor.focus();
191
- }
192
- }
193
-
194
- function toggle(anchor: HTMLElement): void {
195
- const content = findAncestor(anchor, '[oui-popover-content]');
196
-
197
- if (content instanceof HTMLElement) {
198
- closeAbove(content);
199
- } else {
200
- closeAll();
201
- }
202
-
203
- const floatable = mapped.get(anchor);
144
+ //
204
145
 
205
- if (floatable == null) {
206
- return;
207
- }
146
+ const ATTRIBUTE_ID_PREFIX = 'oui_popover_content_';
208
147
 
209
- if (floatable.active) {
210
- deactivate(floatable, active, order);
211
- } else {
212
- activate(floatable, active, order);
213
- focus(floatable.content);
214
- }
215
- }
148
+ const POPOVERS: Map<HTMLElement, Floatable> = new Map();
216
149
 
217
- //
150
+ const SELECTOR = 'oui-popover';
218
151
 
219
- customElements.define('oui-popover', OuiPopoverElement);
152
+ const SELECTOR_CONTENT = `:scope > [${SELECTOR}-content]`;
220
153
 
221
- on(document, 'click', onClick);
222
- on(document, 'keydown', onKeydown);
154
+ const SELECTOR_TOGGLE = `:scope > [${SELECTOR}-toggle]`;
223
155
 
224
156
  //
225
157
 
226
- const active = new Set<Floatable>();
227
-
228
- const order: Floatable[] = [];
158
+ customElements.define(SELECTOR, OuiPopoverElement);