@oscarpalmer/oui 0.23.1 → 0.25.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.
- package/dist/css/{tooltip.css → floatable.css} +59 -24
- package/dist/css/reset.css +17 -0
- package/dist/js/dialog.d.mts +38 -0
- package/dist/js/dialog.mjs +92 -0
- package/dist/js/embedded-84gxpReS.d.mts +64 -0
- package/dist/js/embedded-BpwzMSEF.d.mts +26 -0
- package/dist/js/floatable/embedded.d.mts +2 -0
- package/dist/js/floatable/embedded.mjs +146 -0
- package/dist/js/floatable/standalone.d.mts +2 -0
- package/dist/js/floatable/standalone.mjs +2 -0
- package/dist/js/focus-trap/embedded.d.mts +1 -25
- package/dist/js/focus-trap/embedded.mjs +1 -1
- package/dist/js/index.d.mts +5 -2
- package/dist/js/index.mjs +7 -4
- package/dist/js/internal/attributable.mjs +4 -3
- package/dist/js/internal/draggable.mjs +1 -1
- package/dist/js/popover.d.mts +38 -1
- package/dist/js/popover.mjs +81 -38
- package/dist/js/sortable.mjs +7 -5
- package/dist/js/splitter.mjs +18 -14
- package/dist/js/tooltip.d.mts +33 -1
- package/dist/js/tooltip.mjs +91 -48
- package/package.json +2 -2
- package/src/css/{tooltip.scss → floatable.scss} +47 -5
- package/src/css/mixins.scss +0 -2
- package/src/css/reset.scss +17 -0
- package/src/js/dialog.ts +188 -0
- package/src/js/floatable/embedded.ts +302 -0
- package/src/js/floatable/standalone.ts +1 -0
- package/src/js/index.ts +4 -2
- package/src/js/internal/attributable.ts +5 -3
- package/src/js/popover.ts +146 -48
- package/src/js/sortable.ts +8 -5
- package/src/js/splitter.ts +26 -9
- package/src/js/tooltip.ts +138 -51
- package/dist/css/popover.css +0 -30
- package/dist/js/internal/floatable.d.mts +0 -18
- package/dist/js/internal/floatable.mjs +0 -67
- package/src/css/popover.scss +0 -10
- package/src/js/internal/floatable.ts +0 -118
package/src/js/popover.ts
CHANGED
|
@@ -1,62 +1,136 @@
|
|
|
1
1
|
import {on} from '@oscarpalmer/toretto/event';
|
|
2
|
+
import {isHTMLOrSVGElement} from '@oscarpalmer/toretto/is';
|
|
2
3
|
import type {RemovableEventListener} from '@oscarpalmer/toretto/models';
|
|
3
|
-
import {createFocusTrap, removeFocusTrap, type FocusTrap} from './focus-trap/embedded';
|
|
4
|
-
import {attributable} from './internal/attributable';
|
|
5
4
|
import {
|
|
5
|
+
ATTRIBUTE_FLOATABLE,
|
|
6
6
|
createFloatable,
|
|
7
|
-
|
|
8
|
-
type
|
|
9
|
-
type
|
|
10
|
-
} from './
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
anchor: HTMLElement | null;
|
|
14
|
-
floatable?: Floatable;
|
|
15
|
-
focusTrap?: FocusTrap;
|
|
16
|
-
listener: RemovableEventListener;
|
|
17
|
-
|
|
18
|
-
constructor(public element: HTMLElement) {
|
|
19
|
-
this.anchor = element.ownerDocument.querySelector(`[popovertarget="${element.id}"]`);
|
|
7
|
+
getOnBeforeToggleListener,
|
|
8
|
+
type OuiFloatable,
|
|
9
|
+
type OuiFloatableOptions,
|
|
10
|
+
} from './floatable/embedded';
|
|
11
|
+
import {createFocusTrap, type FocusTrap} from './focus-trap/embedded';
|
|
12
|
+
import {attributable} from './internal/attributable';
|
|
20
13
|
|
|
21
|
-
|
|
14
|
+
// #region Types
|
|
22
15
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
});
|
|
16
|
+
class OuiPopover {
|
|
17
|
+
readonly #state: OuiPopoverState;
|
|
26
18
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
19
|
+
/**
|
|
20
|
+
* Is the popover open?
|
|
21
|
+
*/
|
|
22
|
+
get open(): boolean {
|
|
23
|
+
return this.#state.content.checkVisibility();
|
|
24
|
+
}
|
|
30
25
|
|
|
31
|
-
|
|
26
|
+
constructor(state: OuiPopoverState) {
|
|
27
|
+
this.#state = state;
|
|
32
28
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
this.anchor?.focus();
|
|
36
|
-
}
|
|
37
|
-
});
|
|
29
|
+
this.#state.floatable.update();
|
|
30
|
+
}
|
|
38
31
|
|
|
39
|
-
|
|
32
|
+
/**
|
|
33
|
+
* Closes the popover
|
|
34
|
+
*/
|
|
35
|
+
hide(): void {
|
|
36
|
+
this.#state.floatable.hide();
|
|
37
|
+
}
|
|
40
38
|
|
|
41
|
-
|
|
39
|
+
/**
|
|
40
|
+
* Opens the popover
|
|
41
|
+
*/
|
|
42
|
+
show(): void {
|
|
43
|
+
this.#state.floatable.show();
|
|
42
44
|
}
|
|
45
|
+
}
|
|
43
46
|
|
|
44
|
-
|
|
45
|
-
|
|
47
|
+
type OuiPopoverState = {
|
|
48
|
+
anchor: HTMLElement;
|
|
49
|
+
content: HTMLElement;
|
|
50
|
+
floatable: OuiFloatable;
|
|
51
|
+
focusTrap?: FocusTrap;
|
|
52
|
+
listeners: RemovableEventListener[];
|
|
53
|
+
popover: OuiPopover;
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
// #endregion
|
|
46
57
|
|
|
47
|
-
|
|
48
|
-
removeFocusTrap(this.element);
|
|
58
|
+
// #region Functions
|
|
49
59
|
|
|
50
|
-
|
|
60
|
+
function destroyPopover(state: OuiPopoverState): void {
|
|
61
|
+
state.floatable.destroy();
|
|
62
|
+
state.focusTrap?.destroy();
|
|
51
63
|
|
|
52
|
-
|
|
53
|
-
|
|
64
|
+
for (const listener of state.listeners) {
|
|
65
|
+
listener();
|
|
54
66
|
}
|
|
67
|
+
|
|
68
|
+
state.anchor = undefined as never;
|
|
69
|
+
state.content = undefined as never;
|
|
70
|
+
state.floatable = undefined as never;
|
|
71
|
+
state.focusTrap = undefined as never;
|
|
72
|
+
state.listeners = undefined as never;
|
|
73
|
+
state.popover = undefined as never;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Get the _OuiPopover_ for an element
|
|
78
|
+
*
|
|
79
|
+
* @param element Element to get _OuiPopover_ for
|
|
80
|
+
* @returns _OuiPopover_ instance
|
|
81
|
+
*/
|
|
82
|
+
function getPopover(element: HTMLElement): OuiPopover | undefined {
|
|
83
|
+
return states.get(element)?.popover;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function getState(anchor: HTMLElement, content: HTMLElement): OuiPopoverState {
|
|
87
|
+
const state: OuiPopoverState = {
|
|
88
|
+
anchor,
|
|
89
|
+
content,
|
|
90
|
+
floatable: createFloatable(anchor, content, options, false),
|
|
91
|
+
focusTrap:
|
|
92
|
+
content.getAttribute(ARIA_MODAL) === TRUE
|
|
93
|
+
? createFocusTrap(content, {
|
|
94
|
+
noescape: true,
|
|
95
|
+
})
|
|
96
|
+
: undefined,
|
|
97
|
+
listeners: [
|
|
98
|
+
getOnBeforeToggleListener(content),
|
|
99
|
+
on(content, EVENT_TOGGLE, event => {
|
|
100
|
+
if (event.newState === STATE_OPEN) {
|
|
101
|
+
ignoreFocus = false;
|
|
102
|
+
|
|
103
|
+
state.floatable?.update();
|
|
104
|
+
|
|
105
|
+
content.setAttribute(ATTRIBUTE_OPEN, '');
|
|
106
|
+
|
|
107
|
+
content.focus();
|
|
108
|
+
} else {
|
|
109
|
+
content.removeAttribute(ATTRIBUTE_OPEN);
|
|
110
|
+
|
|
111
|
+
if (!ignoreFocus) {
|
|
112
|
+
anchor?.focus();
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}),
|
|
116
|
+
],
|
|
117
|
+
popover: undefined as never,
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
state.popover = new OuiPopover(state);
|
|
121
|
+
|
|
122
|
+
return state;
|
|
55
123
|
}
|
|
56
124
|
|
|
57
125
|
function onAdd(element: HTMLElement): void {
|
|
58
|
-
if (
|
|
59
|
-
|
|
126
|
+
if (states.has(element) || element.hasAttribute(ATTRIBUTE_FLOATABLE)) {
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const anchor = element.ownerDocument.querySelector(`[popovertarget="${element.id}"]`);
|
|
131
|
+
|
|
132
|
+
if (isHTMLOrSVGElement(anchor)) {
|
|
133
|
+
states.set(element, getState(anchor, element));
|
|
60
134
|
}
|
|
61
135
|
}
|
|
62
136
|
|
|
@@ -65,32 +139,56 @@ function onPointerdown(): void {
|
|
|
65
139
|
}
|
|
66
140
|
|
|
67
141
|
function onRemove(element: HTMLElement): void {
|
|
68
|
-
|
|
142
|
+
const state = states.get(element);
|
|
143
|
+
|
|
144
|
+
if (state == null) {
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
69
147
|
|
|
70
|
-
|
|
148
|
+
destroyPopover(state);
|
|
149
|
+
|
|
150
|
+
states.delete(element);
|
|
71
151
|
}
|
|
72
152
|
|
|
73
|
-
|
|
153
|
+
// #endregion
|
|
74
154
|
|
|
75
|
-
|
|
155
|
+
// #region Variables
|
|
76
156
|
|
|
77
|
-
const
|
|
157
|
+
const ATTRIBUTE = 'oui-popover';
|
|
158
|
+
|
|
159
|
+
const ARIA_MODAL = 'aria-modal';
|
|
160
|
+
|
|
161
|
+
const ATTRIBUTE_OPEN = `${ATTRIBUTE}-open`;
|
|
78
162
|
|
|
79
|
-
const
|
|
163
|
+
const EVENT_TOGGLE = 'toggle';
|
|
80
164
|
|
|
81
165
|
const STATE_OPEN = 'open';
|
|
82
166
|
|
|
83
|
-
const
|
|
167
|
+
const TRUE = 'true';
|
|
84
168
|
|
|
85
|
-
const options:
|
|
86
|
-
attribute:
|
|
169
|
+
const options: OuiFloatableOptions = {
|
|
170
|
+
attribute: `${ATTRIBUTE}position`,
|
|
87
171
|
position: 'below-start',
|
|
88
172
|
};
|
|
89
173
|
|
|
174
|
+
const states = new WeakMap<HTMLElement, OuiPopoverState>();
|
|
175
|
+
|
|
90
176
|
let ignoreFocus = false;
|
|
91
177
|
|
|
178
|
+
// #endregion
|
|
179
|
+
|
|
180
|
+
// #region Initialization
|
|
181
|
+
|
|
92
182
|
on(document, 'pointerdown', onPointerdown, {
|
|
93
183
|
capture: true,
|
|
94
184
|
});
|
|
95
185
|
|
|
96
186
|
attributable(ATTRIBUTE, onAdd, onRemove);
|
|
187
|
+
|
|
188
|
+
// #endregion
|
|
189
|
+
|
|
190
|
+
// #region Exports
|
|
191
|
+
|
|
192
|
+
export {getPopover, type OuiPopover};
|
|
193
|
+
|
|
194
|
+
// #endregion
|
package/src/js/sortable.ts
CHANGED
|
@@ -2,6 +2,7 @@ import type {EventPosition, PlainObject} from '@oscarpalmer/atoms/models';
|
|
|
2
2
|
import {createElement} from '@oscarpalmer/toretto/create';
|
|
3
3
|
import {getPosition} from '@oscarpalmer/toretto/event';
|
|
4
4
|
import {findAncestor, getElementFromPosition} from '@oscarpalmer/toretto/find';
|
|
5
|
+
import {isHTMLOrSVGElement} from '@oscarpalmer/toretto/is';
|
|
5
6
|
import {attributable} from './internal/attributable';
|
|
6
7
|
import {
|
|
7
8
|
addDraggable,
|
|
@@ -333,17 +334,19 @@ function setPlaceholder(state: DraggableState, sortable: Sortable, origin: HTMLE
|
|
|
333
334
|
parent: sortable.element,
|
|
334
335
|
placeholder: element,
|
|
335
336
|
create: (value: unknown) => {
|
|
336
|
-
if (!(value
|
|
337
|
+
if (!isHTMLOrSVGElement(value)) {
|
|
337
338
|
return;
|
|
338
339
|
}
|
|
339
340
|
|
|
340
|
-
|
|
341
|
+
const element = value as HTMLElement;
|
|
341
342
|
|
|
342
|
-
|
|
343
|
+
placeholder.drag.custom = element;
|
|
344
|
+
|
|
345
|
+
updatePlaceholder(sortable, element);
|
|
343
346
|
|
|
344
347
|
state.element = {
|
|
345
|
-
rectangle:
|
|
346
|
-
node:
|
|
348
|
+
rectangle: element.getBoundingClientRect(),
|
|
349
|
+
node: element,
|
|
347
350
|
};
|
|
348
351
|
},
|
|
349
352
|
},
|
package/src/js/splitter.ts
CHANGED
|
@@ -12,8 +12,21 @@ declare global {
|
|
|
12
12
|
}
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
+
const ATTRIBUTE_MAX = 'max';
|
|
16
|
+
|
|
17
|
+
const ATTRIBUTE_MIN = 'min';
|
|
18
|
+
|
|
19
|
+
const ATTRIBUTE_SIZE = 'size';
|
|
20
|
+
|
|
21
|
+
const ATTRIBUTE_TYPE = 'type';
|
|
22
|
+
|
|
15
23
|
export class OuiSplitterElement extends HTMLElement {
|
|
16
|
-
static readonly observedAttributes = [
|
|
24
|
+
static readonly observedAttributes = [
|
|
25
|
+
ATTRIBUTE_MAX,
|
|
26
|
+
ATTRIBUTE_MIN,
|
|
27
|
+
ATTRIBUTE_SIZE,
|
|
28
|
+
ATTRIBUTE_TYPE,
|
|
29
|
+
];
|
|
17
30
|
|
|
18
31
|
readonly #splitter: Splitter;
|
|
19
32
|
|
|
@@ -24,7 +37,7 @@ export class OuiSplitterElement extends HTMLElement {
|
|
|
24
37
|
}
|
|
25
38
|
|
|
26
39
|
set max(value: number) {
|
|
27
|
-
this.setAttribute(
|
|
40
|
+
this.setAttribute(ATTRIBUTE_MAX, String(value));
|
|
28
41
|
}
|
|
29
42
|
|
|
30
43
|
get min(): number {
|
|
@@ -77,13 +90,13 @@ export class OuiSplitterElement extends HTMLElement {
|
|
|
77
90
|
|
|
78
91
|
attributeChangedCallback(name: string): void {
|
|
79
92
|
switch (name) {
|
|
80
|
-
case
|
|
81
|
-
case
|
|
82
|
-
case
|
|
93
|
+
case ATTRIBUTE_MAX:
|
|
94
|
+
case ATTRIBUTE_MIN:
|
|
95
|
+
case ATTRIBUTE_SIZE:
|
|
83
96
|
this.#splitter.updateValues();
|
|
84
97
|
break;
|
|
85
98
|
|
|
86
|
-
case
|
|
99
|
+
case ATTRIBUTE_TYPE:
|
|
87
100
|
this.#splitter.updateOrientation();
|
|
88
101
|
break;
|
|
89
102
|
|
|
@@ -271,15 +284,19 @@ function getValue(element: HTMLElement, name: string, defaultValue: number): num
|
|
|
271
284
|
|
|
272
285
|
function getValues(element: OuiSplitterElement): Values {
|
|
273
286
|
const values: Values = {
|
|
274
|
-
max: getValue(element,
|
|
275
|
-
min: getValue(element,
|
|
287
|
+
max: getValue(element, ATTRIBUTE_MAX, NAVIGATION_MAXIMUM_PERCENTAGE),
|
|
288
|
+
min: getValue(element, ATTRIBUTE_MIN, NAVIGATION_MINIMUM_PERCENTAGE),
|
|
276
289
|
now: {
|
|
277
290
|
current: 0.5,
|
|
278
291
|
previous: 0.5,
|
|
279
292
|
},
|
|
280
293
|
};
|
|
281
294
|
|
|
282
|
-
const value = getContained(
|
|
295
|
+
const value = getContained(
|
|
296
|
+
values.min,
|
|
297
|
+
getValue(element, ATTRIBUTE_SIZE, values.now.current),
|
|
298
|
+
values.max,
|
|
299
|
+
);
|
|
283
300
|
|
|
284
301
|
values.now.current = value;
|
|
285
302
|
values.now.previous = value;
|
package/src/js/tooltip.ts
CHANGED
|
@@ -2,97 +2,136 @@ import {isNullableOrWhitespace} from '@oscarpalmer/atoms/is';
|
|
|
2
2
|
import {createElement} from '@oscarpalmer/toretto/create';
|
|
3
3
|
import {on} from '@oscarpalmer/toretto/event';
|
|
4
4
|
import {findAncestor} from '@oscarpalmer/toretto/find';
|
|
5
|
+
import {isHTMLOrSVGElement} from '@oscarpalmer/toretto/is';
|
|
6
|
+
import {createFloatable, type OuiFloatable, type OuiFloatableOptions} from './floatable/embedded';
|
|
5
7
|
import {attributable} from './internal/attributable';
|
|
6
|
-
import {Floatable, type FloatableOptions} from './internal/floatable';
|
|
7
8
|
|
|
8
|
-
|
|
9
|
-
floatable: Floatable;
|
|
9
|
+
// #region Types
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
class OuiTooltip {
|
|
12
|
+
readonly #state: OuiTooltipState;
|
|
12
13
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
) {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
anchor.insertAdjacentElement('afterend', content);
|
|
14
|
+
/**
|
|
15
|
+
* Is the tooltip open?
|
|
16
|
+
*/
|
|
17
|
+
get open(): boolean {
|
|
18
|
+
return this.#state.content.checkVisibility();
|
|
19
|
+
}
|
|
20
20
|
|
|
21
|
-
|
|
21
|
+
constructor(state: OuiTooltipState) {
|
|
22
|
+
this.#state = state;
|
|
22
23
|
|
|
23
|
-
|
|
24
|
+
state.floatable.update();
|
|
24
25
|
}
|
|
25
26
|
|
|
26
|
-
|
|
27
|
-
|
|
27
|
+
/**
|
|
28
|
+
* Closes the tooltip
|
|
29
|
+
*/
|
|
30
|
+
hide(): void {
|
|
31
|
+
this.#state.floatable.hide();
|
|
32
|
+
}
|
|
28
33
|
|
|
29
|
-
|
|
30
|
-
|
|
34
|
+
/**
|
|
35
|
+
* Opens the tooltip
|
|
36
|
+
*/
|
|
37
|
+
show(): void {
|
|
38
|
+
this.#state.floatable.show();
|
|
31
39
|
}
|
|
32
40
|
}
|
|
33
41
|
|
|
42
|
+
type OuiTooltipState = {
|
|
43
|
+
anchor: HTMLElement;
|
|
44
|
+
content: HTMLElement;
|
|
45
|
+
floatable: OuiFloatable;
|
|
46
|
+
timer?: number;
|
|
47
|
+
tooltip: OuiTooltip;
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
// #endregion
|
|
51
|
+
|
|
52
|
+
// #region Functions
|
|
53
|
+
|
|
34
54
|
function activate(event: Event, focus: boolean): void {
|
|
35
55
|
const anchor = findAncestor(event, SELECTOR) as HTMLElement | null;
|
|
36
56
|
const content = findAncestor(event, SELECTOR_CONTENT) as HTMLElement | null;
|
|
37
57
|
|
|
38
58
|
if (anchor == null && content == null) {
|
|
39
59
|
if (!focused) {
|
|
40
|
-
closeTooltip(
|
|
60
|
+
closeTooltip(activeState);
|
|
41
61
|
|
|
42
|
-
|
|
62
|
+
activeState = undefined;
|
|
63
|
+
activeTooltip = undefined;
|
|
43
64
|
}
|
|
44
65
|
|
|
45
66
|
return;
|
|
46
67
|
}
|
|
47
68
|
|
|
48
|
-
const
|
|
69
|
+
const state = states.get((anchor ?? content)!);
|
|
49
70
|
|
|
50
|
-
if (
|
|
71
|
+
if (state == null || state.tooltip === activeTooltip) {
|
|
51
72
|
return;
|
|
52
73
|
}
|
|
53
74
|
|
|
54
|
-
const shouldDelay =
|
|
75
|
+
const shouldDelay = activeTooltip == null;
|
|
55
76
|
|
|
56
|
-
closeTooltip(
|
|
77
|
+
closeTooltip(activeState);
|
|
78
|
+
|
|
79
|
+
activeState = state;
|
|
80
|
+
activeTooltip = state.tooltip;
|
|
57
81
|
|
|
58
|
-
active = instance;
|
|
59
82
|
focused = focus;
|
|
60
83
|
|
|
61
|
-
|
|
84
|
+
state.timer = setTimeout(
|
|
62
85
|
() => {
|
|
63
|
-
|
|
64
|
-
source:
|
|
86
|
+
state.content.showPopover({
|
|
87
|
+
source: state.anchor as HTMLElement,
|
|
65
88
|
});
|
|
66
89
|
|
|
67
90
|
setTimeout(() => {
|
|
68
|
-
|
|
91
|
+
state.floatable.update();
|
|
69
92
|
|
|
70
|
-
|
|
93
|
+
state.content.setAttribute(ATTRIBUTE_CONTENT_OPEN, '');
|
|
71
94
|
});
|
|
72
95
|
},
|
|
73
96
|
shouldDelay ? delay : 0,
|
|
74
97
|
);
|
|
75
98
|
}
|
|
76
99
|
|
|
77
|
-
function closeTooltip(
|
|
78
|
-
if (
|
|
100
|
+
function closeTooltip(state?: OuiTooltipState): void {
|
|
101
|
+
if (state == null) {
|
|
79
102
|
return;
|
|
80
103
|
}
|
|
81
104
|
|
|
82
|
-
if (
|
|
83
|
-
clearTimeout(
|
|
105
|
+
if (state.timer != null) {
|
|
106
|
+
clearTimeout(state.timer);
|
|
84
107
|
}
|
|
85
108
|
|
|
86
|
-
|
|
87
|
-
|
|
109
|
+
state.content?.hidePopover();
|
|
110
|
+
state.content?.removeAttribute(ATTRIBUTE_CONTENT_OPEN);
|
|
88
111
|
}
|
|
89
112
|
|
|
90
113
|
function createTooltip(anchor: HTMLElement): void {
|
|
91
114
|
const content = getContent(anchor);
|
|
92
115
|
|
|
93
|
-
if (content
|
|
94
|
-
|
|
116
|
+
if (content == null) {
|
|
117
|
+
return;
|
|
95
118
|
}
|
|
119
|
+
|
|
120
|
+
anchor.insertAdjacentElement('afterend', content);
|
|
121
|
+
|
|
122
|
+
states.set(anchor, getState(anchor, content));
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function destroyTooltip(state: OuiTooltipState): void {
|
|
126
|
+
clearTimeout(state.timer);
|
|
127
|
+
|
|
128
|
+
state.content.remove();
|
|
129
|
+
state.floatable.destroy();
|
|
130
|
+
|
|
131
|
+
state.anchor = undefined as never;
|
|
132
|
+
state.content = undefined as never;
|
|
133
|
+
state.floatable = undefined as never;
|
|
134
|
+
state.tooltip = undefined as never;
|
|
96
135
|
}
|
|
97
136
|
|
|
98
137
|
function findElements(attribute: string | null): HTMLElement[] {
|
|
@@ -106,7 +145,7 @@ function findElements(attribute: string | null): HTMLElement[] {
|
|
|
106
145
|
|
|
107
146
|
const element = document.querySelector(`#${id}`);
|
|
108
147
|
|
|
109
|
-
if (element
|
|
148
|
+
if (isHTMLOrSVGElement(element)) {
|
|
110
149
|
const cloned = element.cloneNode(true) as HTMLElement;
|
|
111
150
|
|
|
112
151
|
cloned.hidden = false;
|
|
@@ -168,8 +207,25 @@ function getElements(anchor: HTMLElement): HTMLElement[] | undefined {
|
|
|
168
207
|
return [paragraph];
|
|
169
208
|
}
|
|
170
209
|
|
|
210
|
+
function getState(anchor: HTMLElement, content: HTMLElement): OuiTooltipState {
|
|
211
|
+
const state: OuiTooltipState = {
|
|
212
|
+
anchor,
|
|
213
|
+
content,
|
|
214
|
+
floatable: createFloatable(anchor, content, options, false),
|
|
215
|
+
tooltip: null as never,
|
|
216
|
+
};
|
|
217
|
+
|
|
218
|
+
state.tooltip = new OuiTooltip(state);
|
|
219
|
+
|
|
220
|
+
return state;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
function getTooltip(element: HTMLElement): OuiTooltip | undefined {
|
|
224
|
+
return states.get(element)?.tooltip;
|
|
225
|
+
}
|
|
226
|
+
|
|
171
227
|
function onAdd(element: HTMLElement): void {
|
|
172
|
-
if (!
|
|
228
|
+
if (!states.has(element)) {
|
|
173
229
|
createTooltip(element);
|
|
174
230
|
}
|
|
175
231
|
}
|
|
@@ -179,7 +235,7 @@ function onFocusin(event: Event): void {
|
|
|
179
235
|
}
|
|
180
236
|
|
|
181
237
|
function onFocusout(): void {
|
|
182
|
-
if (
|
|
238
|
+
if (activeTooltip == null) {
|
|
183
239
|
return;
|
|
184
240
|
}
|
|
185
241
|
|
|
@@ -189,16 +245,17 @@ function onFocusout(): void {
|
|
|
189
245
|
|
|
190
246
|
focusTimer = setTimeout(() => {
|
|
191
247
|
if (
|
|
192
|
-
|
|
248
|
+
activeTooltip == null ||
|
|
193
249
|
document.activeElement == null ||
|
|
194
250
|
findAncestor(document.activeElement, SELECTOR) != null
|
|
195
251
|
) {
|
|
196
252
|
return;
|
|
197
253
|
}
|
|
198
254
|
|
|
199
|
-
closeTooltip(
|
|
255
|
+
closeTooltip(activeState);
|
|
200
256
|
|
|
201
|
-
|
|
257
|
+
activeState = undefined;
|
|
258
|
+
activeTooltip = undefined;
|
|
202
259
|
});
|
|
203
260
|
}
|
|
204
261
|
|
|
@@ -207,17 +264,33 @@ function onPointermove(event: Event): void {
|
|
|
207
264
|
}
|
|
208
265
|
|
|
209
266
|
function onRemove(element: HTMLElement): void {
|
|
210
|
-
|
|
267
|
+
const state = states.get(element);
|
|
211
268
|
|
|
212
|
-
|
|
269
|
+
if (state == null) {
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
destroyTooltip(state);
|
|
274
|
+
|
|
275
|
+
states.delete(element);
|
|
213
276
|
}
|
|
214
277
|
|
|
215
|
-
|
|
278
|
+
/**
|
|
279
|
+
* Set the delay before showing the tooltip, in milliseconds
|
|
280
|
+
*
|
|
281
|
+
* _(Default delay is half a second, or 500 milliseconds)_
|
|
282
|
+
* @param value Delay in milliseconds
|
|
283
|
+
*/
|
|
284
|
+
function setTooltipDelay(value: number): void {
|
|
216
285
|
if (typeof value === 'number' && !Number.isNaN(value) && value >= 0) {
|
|
217
286
|
delay = value;
|
|
218
287
|
}
|
|
219
288
|
}
|
|
220
289
|
|
|
290
|
+
// #endregion
|
|
291
|
+
|
|
292
|
+
// #region Variables
|
|
293
|
+
|
|
221
294
|
const ARIA_DESCRIBEDBY = 'aria-describedby';
|
|
222
295
|
|
|
223
296
|
const ARIA_DESCRIPTION = 'aria-description';
|
|
@@ -234,16 +307,16 @@ const ATTRIBUTE = 'oui-tooltip';
|
|
|
234
307
|
|
|
235
308
|
const ATTRIBUTE_CONTENT = `${ATTRIBUTE}-content`;
|
|
236
309
|
|
|
237
|
-
const
|
|
310
|
+
const ATTRIBUTE_CONTENT_OPEN = `${ATTRIBUTE_CONTENT}-open`;
|
|
238
311
|
|
|
239
312
|
const SELECTOR = `[${ATTRIBUTE}]`;
|
|
240
313
|
|
|
241
314
|
const SELECTOR_CONTENT = `[${ATTRIBUTE_CONTENT}]`;
|
|
242
315
|
|
|
243
|
-
const
|
|
316
|
+
const states = new WeakMap<HTMLElement, OuiTooltipState>();
|
|
244
317
|
|
|
245
|
-
const options:
|
|
246
|
-
attribute:
|
|
318
|
+
const options: OuiFloatableOptions = {
|
|
319
|
+
attribute: `${ATTRIBUTE}-position`,
|
|
247
320
|
position: 'above',
|
|
248
321
|
};
|
|
249
322
|
|
|
@@ -251,12 +324,26 @@ let delay = 500;
|
|
|
251
324
|
|
|
252
325
|
let focused = false;
|
|
253
326
|
|
|
254
|
-
let
|
|
327
|
+
let activeState: OuiTooltipState | undefined;
|
|
328
|
+
|
|
329
|
+
let activeTooltip: OuiTooltip | undefined;
|
|
255
330
|
|
|
256
331
|
let focusTimer: number | undefined;
|
|
257
332
|
|
|
333
|
+
// #endregion
|
|
334
|
+
|
|
335
|
+
// #region Initialization
|
|
336
|
+
|
|
258
337
|
on(document, 'focusin', onFocusin);
|
|
259
338
|
on(document, 'focusout', onFocusout);
|
|
260
339
|
on(document, 'pointermove', onPointermove);
|
|
261
340
|
|
|
262
341
|
attributable(ATTRIBUTE, onAdd, onRemove);
|
|
342
|
+
|
|
343
|
+
// #endregion
|
|
344
|
+
|
|
345
|
+
// #region Exports
|
|
346
|
+
|
|
347
|
+
export {getTooltip, setTooltipDelay, type OuiTooltip};
|
|
348
|
+
|
|
349
|
+
// #endregion
|
package/dist/css/popover.css
DELETED
|
@@ -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
|
-
|