@oscarpalmer/oui 0.25.0 → 0.26.1
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/js/dialog.d.mts +4 -0
- package/dist/js/dialog.mjs +7 -0
- package/dist/js/embedded-CHK82Uh5.d.mts +76 -0
- package/dist/js/{embedded-84gxpReS.d.mts → embedded-CSOh5njI.d.mts} +25 -21
- package/dist/js/floatable/embedded.d.mts +2 -2
- package/dist/js/floatable/embedded.mjs +31 -20
- package/dist/js/floatable/standalone.d.mts +2 -2
- package/dist/js/floatable/standalone.mjs +2 -2
- package/dist/js/focus-trap/embedded.d.mts +2 -2
- package/dist/js/focus-trap/embedded.mjs +157 -79
- package/dist/js/focus-trap/standalone.d.mts +2 -1
- package/dist/js/focus-trap/standalone.mjs +3 -3
- package/dist/js/index.d.mts +3 -2
- package/dist/js/index.mjs +4 -2
- package/dist/js/internal/draggable.d.mts +2 -2
- package/dist/js/internal/draggable.mjs +2 -2
- package/dist/js/{movable-BKFgoaOl.d.mts → movable-dNqBctx3.d.mts} +16 -16
- package/dist/js/movable.d.mts +2 -2
- package/dist/js/movable.mjs +4 -4
- package/dist/js/popover.d.mts +4 -3
- package/dist/js/popover.mjs +13 -9
- package/dist/js/sortable.d.mts +2 -2
- package/dist/js/sortable.mjs +4 -4
- package/dist/js/splitter.d.mts +24 -0
- package/dist/js/splitter.mjs +76 -51
- package/dist/js/toolbar.d.mts +1 -0
- package/dist/js/toolbar.mjs +107 -0
- package/dist/js/tooltip.d.mts +11 -1
- package/dist/js/tooltip.mjs +20 -7
- package/package.json +13 -1
- package/src/js/dialog.ts +11 -0
- package/src/js/floatable/embedded.ts +69 -48
- package/src/js/floatable/standalone.ts +1 -1
- package/src/js/focus-trap/embedded.ts +219 -93
- package/src/js/focus-trap/standalone.ts +4 -2
- package/src/js/index.ts +1 -0
- package/src/js/internal/draggable.ts +20 -20
- package/src/js/movable.ts +17 -17
- package/src/js/popover.ts +26 -18
- package/src/js/sortable.ts +26 -26
- package/src/js/splitter.ts +126 -100
- package/src/js/toolbar.ts +214 -0
- package/src/js/tooltip.ts +31 -10
- package/dist/js/embedded-BpwzMSEF.d.mts +0 -26
package/src/js/splitter.ts
CHANGED
|
@@ -20,6 +20,83 @@ const ATTRIBUTE_SIZE = 'size';
|
|
|
20
20
|
|
|
21
21
|
const ATTRIBUTE_TYPE = 'type';
|
|
22
22
|
|
|
23
|
+
class OuiSplitter {
|
|
24
|
+
element: OuiSplitterElement;
|
|
25
|
+
|
|
26
|
+
handle: HTMLSpanElement;
|
|
27
|
+
|
|
28
|
+
panels: HTMLElement[];
|
|
29
|
+
|
|
30
|
+
rectangle: DOMRect;
|
|
31
|
+
|
|
32
|
+
separator: HTMLDivElement;
|
|
33
|
+
|
|
34
|
+
types!: OuiSplitterTypes;
|
|
35
|
+
|
|
36
|
+
values: OuiSplitterValues;
|
|
37
|
+
|
|
38
|
+
constructor(element: OuiSplitterElement, panels: HTMLElement[]) {
|
|
39
|
+
this.element = element;
|
|
40
|
+
this.panels = panels;
|
|
41
|
+
|
|
42
|
+
if (isNullableOrWhitespace(element.id)) {
|
|
43
|
+
element.id = `oui_splitter_${++index}`;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
this.values = getValues(element);
|
|
47
|
+
|
|
48
|
+
this.handle = createHandle();
|
|
49
|
+
this.rectangle = element.getBoundingClientRect();
|
|
50
|
+
this.separator = createSeparator(this);
|
|
51
|
+
|
|
52
|
+
this.separator.append(this.handle);
|
|
53
|
+
|
|
54
|
+
panels[0].insertAdjacentElement('afterend', this.separator);
|
|
55
|
+
|
|
56
|
+
this.updateOrientation();
|
|
57
|
+
this.updateValues();
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
updateConnections(add: 0 | 1): void {
|
|
61
|
+
const method = METHODS_CONNECT[add];
|
|
62
|
+
|
|
63
|
+
MAPPED_ELEMENTS[method](this.element, this);
|
|
64
|
+
MAPPED_ELEMENTS[method](this.handle, this);
|
|
65
|
+
MAPPED_ELEMENTS[method](this.separator, this);
|
|
66
|
+
|
|
67
|
+
if (this.types.auto) {
|
|
68
|
+
RESIZE_OBSERVER[METHODS_OBSERVE[add]](this.element);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
updateOrientation(): void {
|
|
73
|
+
this.types = getTypes(this);
|
|
74
|
+
|
|
75
|
+
if (this.types.auto) {
|
|
76
|
+
RESIZE_OBSERVER.observe(this.element);
|
|
77
|
+
} else {
|
|
78
|
+
RESIZE_OBSERVER.unobserve(this.element);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (this.types.horizontal) {
|
|
82
|
+
this.element.setAttribute(ATTRIBUTE_HORIZONTAL, '');
|
|
83
|
+
} else {
|
|
84
|
+
this.element.removeAttribute(ATTRIBUTE_HORIZONTAL);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
setAriaOrientation(this);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
updateValues(): void {
|
|
91
|
+
this.values = getValues(this.element);
|
|
92
|
+
|
|
93
|
+
requestAnimationFrame(() => {
|
|
94
|
+
setSize(this, this.values.now.current);
|
|
95
|
+
setAriaValue(this);
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
23
100
|
export class OuiSplitterElement extends HTMLElement {
|
|
24
101
|
static readonly observedAttributes = [
|
|
25
102
|
ATTRIBUTE_MAX,
|
|
@@ -28,40 +105,64 @@ export class OuiSplitterElement extends HTMLElement {
|
|
|
28
105
|
ATTRIBUTE_TYPE,
|
|
29
106
|
];
|
|
30
107
|
|
|
31
|
-
readonly #splitter:
|
|
108
|
+
readonly #splitter: OuiSplitter;
|
|
32
109
|
|
|
33
110
|
// Getters and setters
|
|
34
111
|
|
|
112
|
+
/**
|
|
113
|
+
* Maximum size for the first panel
|
|
114
|
+
*/
|
|
35
115
|
get max(): number {
|
|
36
116
|
return this.#splitter.values.max;
|
|
37
117
|
}
|
|
38
118
|
|
|
119
|
+
/**
|
|
120
|
+
* Maximum size for the first panel
|
|
121
|
+
*/
|
|
39
122
|
set max(value: number) {
|
|
40
123
|
this.setAttribute(ATTRIBUTE_MAX, String(value));
|
|
41
124
|
}
|
|
42
125
|
|
|
126
|
+
/**
|
|
127
|
+
* Minimum size for the first panel
|
|
128
|
+
*/
|
|
43
129
|
get min(): number {
|
|
44
130
|
return this.#splitter.values.min;
|
|
45
131
|
}
|
|
46
132
|
|
|
133
|
+
/**
|
|
134
|
+
* Minimum size for the first panel
|
|
135
|
+
*/
|
|
47
136
|
set min(value: number) {
|
|
48
|
-
this.setAttribute(
|
|
137
|
+
this.setAttribute(ATTRIBUTE_MIN, String(value));
|
|
49
138
|
}
|
|
50
139
|
|
|
140
|
+
/**
|
|
141
|
+
* Current size for the first panel
|
|
142
|
+
*/
|
|
51
143
|
get size(): number {
|
|
52
144
|
return this.#splitter.values.now.current;
|
|
53
145
|
}
|
|
54
146
|
|
|
147
|
+
/**
|
|
148
|
+
* Current size for the first panel
|
|
149
|
+
*/
|
|
55
150
|
set size(value: number) {
|
|
56
|
-
this.setAttribute(
|
|
151
|
+
this.setAttribute(ATTRIBUTE_SIZE, String(value));
|
|
57
152
|
}
|
|
58
153
|
|
|
154
|
+
/**
|
|
155
|
+
* Orientation of the splitter
|
|
156
|
+
*/
|
|
59
157
|
get type(): string {
|
|
60
|
-
return this.getAttribute(
|
|
158
|
+
return this.getAttribute(ATTRIBUTE_TYPE) ?? 'vertical';
|
|
61
159
|
}
|
|
62
160
|
|
|
161
|
+
/**
|
|
162
|
+
* Orientation of the splitter
|
|
163
|
+
*/
|
|
63
164
|
set type(value: string) {
|
|
64
|
-
this.setAttribute(
|
|
165
|
+
this.setAttribute(ATTRIBUTE_TYPE, value);
|
|
65
166
|
}
|
|
66
167
|
|
|
67
168
|
// Constructor
|
|
@@ -72,10 +173,10 @@ export class OuiSplitterElement extends HTMLElement {
|
|
|
72
173
|
const panels = [...this.children].filter(child => child instanceof HTMLElement);
|
|
73
174
|
|
|
74
175
|
if (panels.length !== 2) {
|
|
75
|
-
throw new Error(
|
|
176
|
+
throw new Error(MESSAGE);
|
|
76
177
|
}
|
|
77
178
|
|
|
78
|
-
this.#splitter = new
|
|
179
|
+
this.#splitter = new OuiSplitter(this, panels);
|
|
79
180
|
}
|
|
80
181
|
|
|
81
182
|
// Methods
|
|
@@ -106,97 +207,18 @@ export class OuiSplitterElement extends HTMLElement {
|
|
|
106
207
|
}
|
|
107
208
|
}
|
|
108
209
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
class Splitter {
|
|
112
|
-
element: OuiSplitterElement;
|
|
113
|
-
|
|
114
|
-
handle: HTMLSpanElement;
|
|
115
|
-
|
|
116
|
-
panels: HTMLElement[];
|
|
117
|
-
|
|
118
|
-
rectangle: DOMRect;
|
|
119
|
-
|
|
120
|
-
separator: HTMLDivElement;
|
|
121
|
-
|
|
122
|
-
types!: Types;
|
|
123
|
-
|
|
124
|
-
values: Values;
|
|
125
|
-
|
|
126
|
-
constructor(element: OuiSplitterElement, panels: HTMLElement[]) {
|
|
127
|
-
this.element = element;
|
|
128
|
-
this.panels = panels;
|
|
129
|
-
|
|
130
|
-
if (isNullableOrWhitespace(element.id)) {
|
|
131
|
-
element.id = `oui_splitter_${++index}`;
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
this.values = getValues(element);
|
|
135
|
-
|
|
136
|
-
this.handle = createHandle();
|
|
137
|
-
this.rectangle = element.getBoundingClientRect();
|
|
138
|
-
this.separator = createSeparator(this);
|
|
139
|
-
|
|
140
|
-
this.separator.append(this.handle);
|
|
141
|
-
|
|
142
|
-
panels[0].insertAdjacentElement('afterend', this.separator);
|
|
143
|
-
|
|
144
|
-
this.updateOrientation();
|
|
145
|
-
this.updateValues();
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
updateConnections(add: 0 | 1): void {
|
|
149
|
-
const method = METHODS_CONNECT[add];
|
|
150
|
-
|
|
151
|
-
MAPPED_ELEMENTS[method](this.element, this);
|
|
152
|
-
MAPPED_ELEMENTS[method](this.handle, this);
|
|
153
|
-
MAPPED_ELEMENTS[method](this.separator, this);
|
|
154
|
-
|
|
155
|
-
if (this.types.auto) {
|
|
156
|
-
RESIZE_OBSERVER[METHODS_OBSERVE[add]](this.element);
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
updateOrientation(): void {
|
|
161
|
-
this.types = getTypes(this);
|
|
162
|
-
|
|
163
|
-
if (this.types.auto) {
|
|
164
|
-
RESIZE_OBSERVER.observe(this.element);
|
|
165
|
-
} else {
|
|
166
|
-
RESIZE_OBSERVER.unobserve(this.element);
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
if (this.types.horizontal) {
|
|
170
|
-
this.element.setAttribute(ATTRIBUTE_HORIZONTAL, '');
|
|
171
|
-
} else {
|
|
172
|
-
this.element.removeAttribute(ATTRIBUTE_HORIZONTAL);
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
setAriaOrientation(this);
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
updateValues(): void {
|
|
179
|
-
this.values = getValues(this.element);
|
|
180
|
-
|
|
181
|
-
requestAnimationFrame(() => {
|
|
182
|
-
setSize(this, this.values.now.current);
|
|
183
|
-
setAriaValue(this);
|
|
184
|
-
});
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
type Types = {
|
|
210
|
+
type OuiSplitterTypes = {
|
|
189
211
|
auto: boolean;
|
|
190
212
|
horizontal: boolean;
|
|
191
213
|
};
|
|
192
214
|
|
|
193
|
-
type
|
|
215
|
+
type OuiSplitterValues = {
|
|
194
216
|
max: number;
|
|
195
217
|
min: number;
|
|
196
|
-
now:
|
|
218
|
+
now: OuiSplitterValuesNow;
|
|
197
219
|
};
|
|
198
220
|
|
|
199
|
-
type
|
|
221
|
+
type OuiSplitterValuesNow = {
|
|
200
222
|
current: number;
|
|
201
223
|
previous: number;
|
|
202
224
|
};
|
|
@@ -212,7 +234,7 @@ function createHandle(): HTMLSpanElement {
|
|
|
212
234
|
return handle;
|
|
213
235
|
}
|
|
214
236
|
|
|
215
|
-
function createSeparator(splitter:
|
|
237
|
+
function createSeparator(splitter: OuiSplitter): HTMLDivElement {
|
|
216
238
|
const separator = document.createElement('div');
|
|
217
239
|
|
|
218
240
|
separator.role = 'separator';
|
|
@@ -229,7 +251,7 @@ function getContained(min: number, value: number, max: number): number {
|
|
|
229
251
|
return round(clamp(value, min, max), VALUE_DECIMALS);
|
|
230
252
|
}
|
|
231
253
|
|
|
232
|
-
function getSize(splitter:
|
|
254
|
+
function getSize(splitter: OuiSplitter, value: number): number {
|
|
233
255
|
const {height, left, top, width} = splitter.rectangle;
|
|
234
256
|
|
|
235
257
|
const offset = value - (splitter.types.horizontal ? top : left);
|
|
@@ -239,7 +261,7 @@ function getSize(splitter: Splitter, value: number): number {
|
|
|
239
261
|
return getContained(splitter.values.min, fraction, splitter.values.max);
|
|
240
262
|
}
|
|
241
263
|
|
|
242
|
-
function getTypes(splitter:
|
|
264
|
+
function getTypes(splitter: OuiSplitter, width?: number, height?: number): OuiSplitterTypes {
|
|
243
265
|
const type = splitter.element.getAttribute('type')?.trim() ?? '';
|
|
244
266
|
|
|
245
267
|
if (type === 'horizontal') {
|
|
@@ -282,8 +304,8 @@ function getValue(element: HTMLElement, name: string, defaultValue: number): num
|
|
|
282
304
|
return parsed > 1 ? parsed / NAVIGATION_MAXIMUM_TOTAL : parsed;
|
|
283
305
|
}
|
|
284
306
|
|
|
285
|
-
function getValues(element: OuiSplitterElement):
|
|
286
|
-
const values:
|
|
307
|
+
function getValues(element: OuiSplitterElement): OuiSplitterValues {
|
|
308
|
+
const values: OuiSplitterValues = {
|
|
287
309
|
max: getValue(element, ATTRIBUTE_MAX, NAVIGATION_MAXIMUM_PERCENTAGE),
|
|
288
310
|
min: getValue(element, ATTRIBUTE_MIN, NAVIGATION_MINIMUM_PERCENTAGE),
|
|
289
311
|
now: {
|
|
@@ -445,21 +467,21 @@ function onPointerup(): void {
|
|
|
445
467
|
}
|
|
446
468
|
}
|
|
447
469
|
|
|
448
|
-
function setAriaOrientation(splitter:
|
|
470
|
+
function setAriaOrientation(splitter: OuiSplitter): void {
|
|
449
471
|
splitter.separator.setAttribute(
|
|
450
472
|
'aria-orientation',
|
|
451
473
|
splitter.types.horizontal ? 'horizontal' : 'vertical',
|
|
452
474
|
);
|
|
453
475
|
}
|
|
454
476
|
|
|
455
|
-
function setAriaValue(splitter:
|
|
477
|
+
function setAriaValue(splitter: OuiSplitter): void {
|
|
456
478
|
splitter.separator.setAttribute('aria-valuemax', String(splitter.values.max));
|
|
457
479
|
splitter.separator.setAttribute('aria-valuemin', String(splitter.values.min));
|
|
458
480
|
|
|
459
481
|
splitter.separator.setAttribute('aria-valuenow', String(splitter.values.now.current));
|
|
460
482
|
}
|
|
461
483
|
|
|
462
|
-
function setSize(splitter:
|
|
484
|
+
function setSize(splitter: OuiSplitter, size: number, previous?: boolean): void {
|
|
463
485
|
splitter.values.now.current = size;
|
|
464
486
|
|
|
465
487
|
if (previous) {
|
|
@@ -493,7 +515,9 @@ const KEYS_VERTICAL: Set<string> = new Set(['ArrowLeft', 'ArrowRight']);
|
|
|
493
515
|
|
|
494
516
|
const KEYS_ALL: Set<string> = new Set([...KEYS_ABSOLUTE, ...KEYS_HORIZONTAL, ...KEYS_VERTICAL]);
|
|
495
517
|
|
|
496
|
-
const MAPPED_ELEMENTS: WeakMap<HTMLElement,
|
|
518
|
+
const MAPPED_ELEMENTS: WeakMap<HTMLElement, OuiSplitter> = new WeakMap();
|
|
519
|
+
|
|
520
|
+
const MESSAGE = `<${SELECTOR}> must have exactly two panels.`;
|
|
497
521
|
|
|
498
522
|
const METHODS_CONNECT = ['delete', 'set'] as const;
|
|
499
523
|
|
|
@@ -519,9 +543,11 @@ const STYLING_TOGGLER: StyleToggler = toggleStyles(document.body, {
|
|
|
519
543
|
|
|
520
544
|
const VALUE_DECIMALS = 6;
|
|
521
545
|
|
|
546
|
+
let index = 0;
|
|
547
|
+
|
|
522
548
|
let frame: DOMHighResTimeStamp | undefined;
|
|
523
549
|
|
|
524
|
-
let splitter:
|
|
550
|
+
let splitter: OuiSplitter | undefined;
|
|
525
551
|
|
|
526
552
|
//
|
|
527
553
|
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
import {clamp} from '@oscarpalmer/atoms/number';
|
|
2
|
+
import {on} from '@oscarpalmer/toretto/event';
|
|
3
|
+
import {findAncestor} from '@oscarpalmer/toretto/find';
|
|
4
|
+
import {isHTMLOrSVGElement} from '@oscarpalmer/toretto/is';
|
|
5
|
+
import type {RemovableEventListener} from '@oscarpalmer/toretto/models';
|
|
6
|
+
import {attributable} from './internal/attributable';
|
|
7
|
+
|
|
8
|
+
class OuiToolbar {
|
|
9
|
+
readonly #state: OuiToolbarState;
|
|
10
|
+
|
|
11
|
+
constructor(state: OuiToolbarState) {
|
|
12
|
+
this.#state = state;
|
|
13
|
+
|
|
14
|
+
updateFocus(this.#state, false);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
destroy(): void {
|
|
18
|
+
for (const listener of this.#state.listeners) {
|
|
19
|
+
listener();
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
this.#state.active = undefined as never;
|
|
23
|
+
this.#state.element = undefined as never;
|
|
24
|
+
this.#state.listeners = undefined as never;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
type OuiToolbarOrientation = 'horizontal' | 'vertical';
|
|
29
|
+
|
|
30
|
+
type OuiToolbarState = {
|
|
31
|
+
active: OuiToolbarStateActive;
|
|
32
|
+
element: HTMLElement;
|
|
33
|
+
listeners: RemovableEventListener[];
|
|
34
|
+
orientation: OuiToolbarOrientation;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
type OuiToolbarStateActive = {
|
|
38
|
+
element?: HTMLElement;
|
|
39
|
+
index: number;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
function addToolbar(element: HTMLElement): void {
|
|
43
|
+
if (!instances.has(element)) {
|
|
44
|
+
instances.set(element, new OuiToolbar(getState(element)));
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function getItems(element: HTMLElement): HTMLElement[] {
|
|
49
|
+
return [...element.querySelectorAll<HTMLElement>(SELECTOR)].filter(isValidItem);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function getOrientation(element: HTMLElement): OuiToolbarOrientation {
|
|
53
|
+
const attribute = element.getAttribute(ARIA_ORIENTATION);
|
|
54
|
+
|
|
55
|
+
return ORIENTATIONS.has(attribute as OuiToolbarOrientation)
|
|
56
|
+
? (attribute as OuiToolbarOrientation)
|
|
57
|
+
: ORIENTATION_HORIZONTAL;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function getState(element: HTMLElement): OuiToolbarState {
|
|
61
|
+
const state: OuiToolbarState = {
|
|
62
|
+
element,
|
|
63
|
+
active: {
|
|
64
|
+
index: 0,
|
|
65
|
+
},
|
|
66
|
+
listeners: [
|
|
67
|
+
on(element, EVENT_FOCUSIN, event => {
|
|
68
|
+
onFocusin(state, event);
|
|
69
|
+
}),
|
|
70
|
+
on(
|
|
71
|
+
element,
|
|
72
|
+
EVENT_KEYDOWN,
|
|
73
|
+
event => {
|
|
74
|
+
onKeydown(state, event);
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
passive: false,
|
|
78
|
+
},
|
|
79
|
+
),
|
|
80
|
+
],
|
|
81
|
+
orientation: getOrientation(element),
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
return state;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function isValidItem(element: HTMLElement): boolean {
|
|
88
|
+
return PROPERTY_DISABLED in element ? element.disabled !== true : true;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function onKeydown(state: OuiToolbarState, event: KeyboardEvent): void {
|
|
92
|
+
if (!KEYS_ALL.has(event.key)) {
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const items = getItems(state.element);
|
|
97
|
+
|
|
98
|
+
if (items.length === 0) {
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
let index: number | undefined;
|
|
103
|
+
|
|
104
|
+
if (KEYS_ABSOLUTE.has(event.key)) {
|
|
105
|
+
index = event.key === KEY_HOME ? 0 : items.length - 1;
|
|
106
|
+
} else if (
|
|
107
|
+
state.orientation === ORIENTATION_HORIZONTAL
|
|
108
|
+
? KEYS_HORIZONTAL.has(event.key)
|
|
109
|
+
: KEYS_VERTICAL.has(event.key)
|
|
110
|
+
) {
|
|
111
|
+
if (state.active.element instanceof HTMLSelectElement) {
|
|
112
|
+
event.preventDefault();
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
index = clamp(
|
|
116
|
+
state.active.index + (KEYS_NEXT.has(event.key) ? 1 : -1),
|
|
117
|
+
0,
|
|
118
|
+
items.length - 1,
|
|
119
|
+
true,
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (index == null || index === state.active.index) {
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
state.active.element = items[index];
|
|
128
|
+
state.active.index = index;
|
|
129
|
+
|
|
130
|
+
updateFocus(state, true, items);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function onFocusin(state: OuiToolbarState, event: FocusEvent): void {
|
|
134
|
+
const item = findAncestor(event, SELECTOR);
|
|
135
|
+
|
|
136
|
+
if (!isHTMLOrSVGElement(item) || !isValidItem(item)) {
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const items = getItems(state.element);
|
|
141
|
+
const index = items.indexOf(item);
|
|
142
|
+
|
|
143
|
+
if (index === -1) {
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
state.active.element = item;
|
|
148
|
+
state.active.index = index;
|
|
149
|
+
|
|
150
|
+
updateFocus(state, false, items);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function removeToolbar(element: HTMLElement): void {
|
|
154
|
+
instances.get(element)?.destroy();
|
|
155
|
+
|
|
156
|
+
instances.delete(element);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function updateFocus(state: OuiToolbarState, focus: boolean, items?: HTMLElement[]): void {
|
|
160
|
+
const updated = items ?? getItems(state.element);
|
|
161
|
+
const {length} = updated;
|
|
162
|
+
|
|
163
|
+
if (length === 0) {
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
for (let index = 0; index < length; index += 1) {
|
|
168
|
+
updated[index].tabIndex = index === state.active.index ? 0 : -1;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
state.active.element ??= updated[state.active.index];
|
|
172
|
+
|
|
173
|
+
if (focus) {
|
|
174
|
+
state.active.element?.focus();
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
const ARIA_ORIENTATION = 'aria-orientation';
|
|
179
|
+
|
|
180
|
+
const ATTRIBUTE = 'oui-toolbar';
|
|
181
|
+
|
|
182
|
+
const EVENT_KEYDOWN = 'keydown';
|
|
183
|
+
|
|
184
|
+
const EVENT_FOCUSIN = 'focusin';
|
|
185
|
+
|
|
186
|
+
const KEY_ARROW_DOWN = 'ArrowDown';
|
|
187
|
+
|
|
188
|
+
const KEY_ARROW_RIGHT = 'ArrowRight';
|
|
189
|
+
|
|
190
|
+
const KEY_END = 'End';
|
|
191
|
+
|
|
192
|
+
const KEY_HOME = 'Home';
|
|
193
|
+
|
|
194
|
+
const KEYS_ABSOLUTE = new Set([KEY_END, KEY_HOME]);
|
|
195
|
+
|
|
196
|
+
const KEYS_HORIZONTAL = new Set([KEY_ARROW_RIGHT, 'ArrowLeft']);
|
|
197
|
+
|
|
198
|
+
const KEYS_VERTICAL = new Set([KEY_ARROW_DOWN, 'ArrowUp']);
|
|
199
|
+
|
|
200
|
+
const KEYS_ALL = new Set([...KEYS_ABSOLUTE, ...KEYS_HORIZONTAL, ...KEYS_VERTICAL]);
|
|
201
|
+
|
|
202
|
+
const KEYS_NEXT = new Set([KEY_ARROW_DOWN, KEY_ARROW_RIGHT]);
|
|
203
|
+
|
|
204
|
+
const ORIENTATION_HORIZONTAL: OuiToolbarOrientation = 'horizontal';
|
|
205
|
+
|
|
206
|
+
const ORIENTATIONS = new Set<OuiToolbarOrientation>([ORIENTATION_HORIZONTAL, 'vertical']);
|
|
207
|
+
|
|
208
|
+
const PROPERTY_DISABLED = 'disabled';
|
|
209
|
+
|
|
210
|
+
const SELECTOR = `[${ATTRIBUTE}-item]`;
|
|
211
|
+
|
|
212
|
+
const instances = new WeakMap<HTMLElement, OuiToolbar>();
|
|
213
|
+
|
|
214
|
+
attributable(ATTRIBUTE, addToolbar, removeToolbar);
|
package/src/js/tooltip.ts
CHANGED
|
@@ -3,7 +3,11 @@ 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 {
|
|
6
|
+
import {
|
|
7
|
+
createEmbeddedFloatable,
|
|
8
|
+
type OuiFloatable,
|
|
9
|
+
type OuiFloatableOptions,
|
|
10
|
+
} from './floatable/embedded';
|
|
7
11
|
import {attributable} from './internal/attributable';
|
|
8
12
|
|
|
9
13
|
// #region Types
|
|
@@ -18,6 +22,17 @@ class OuiTooltip {
|
|
|
18
22
|
return this.#state.content.checkVisibility();
|
|
19
23
|
}
|
|
20
24
|
|
|
25
|
+
/**
|
|
26
|
+
* Open or close the tooltip
|
|
27
|
+
*/
|
|
28
|
+
set open(value: boolean) {
|
|
29
|
+
if (value === true) {
|
|
30
|
+
this.show();
|
|
31
|
+
} else if (value === false) {
|
|
32
|
+
this.hide();
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
21
36
|
constructor(state: OuiTooltipState) {
|
|
22
37
|
this.#state = state;
|
|
23
38
|
|
|
@@ -97,6 +112,12 @@ function activate(event: Event, focus: boolean): void {
|
|
|
97
112
|
);
|
|
98
113
|
}
|
|
99
114
|
|
|
115
|
+
function addTooltip(element: HTMLElement): void {
|
|
116
|
+
if (!states.has(element)) {
|
|
117
|
+
createTooltip(element);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
100
121
|
function closeTooltip(state?: OuiTooltipState): void {
|
|
101
122
|
if (state == null) {
|
|
102
123
|
return;
|
|
@@ -211,7 +232,7 @@ function getState(anchor: HTMLElement, content: HTMLElement): OuiTooltipState {
|
|
|
211
232
|
const state: OuiTooltipState = {
|
|
212
233
|
anchor,
|
|
213
234
|
content,
|
|
214
|
-
floatable:
|
|
235
|
+
floatable: createEmbeddedFloatable(anchor, content, options),
|
|
215
236
|
tooltip: null as never,
|
|
216
237
|
};
|
|
217
238
|
|
|
@@ -220,16 +241,16 @@ function getState(anchor: HTMLElement, content: HTMLElement): OuiTooltipState {
|
|
|
220
241
|
return state;
|
|
221
242
|
}
|
|
222
243
|
|
|
244
|
+
/**
|
|
245
|
+
* Get the _OuiTooltip_ for an element
|
|
246
|
+
*
|
|
247
|
+
* @param element Element to get _OuiTooltip_ for
|
|
248
|
+
* @returns _OuiTooltip_ instance
|
|
249
|
+
*/
|
|
223
250
|
function getTooltip(element: HTMLElement): OuiTooltip | undefined {
|
|
224
251
|
return states.get(element)?.tooltip;
|
|
225
252
|
}
|
|
226
253
|
|
|
227
|
-
function onAdd(element: HTMLElement): void {
|
|
228
|
-
if (!states.has(element)) {
|
|
229
|
-
createTooltip(element);
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
|
|
233
254
|
function onFocusin(event: Event): void {
|
|
234
255
|
activate(event, true);
|
|
235
256
|
}
|
|
@@ -263,7 +284,7 @@ function onPointermove(event: Event): void {
|
|
|
263
284
|
activate(event, false);
|
|
264
285
|
}
|
|
265
286
|
|
|
266
|
-
function
|
|
287
|
+
function removeTooltip(element: HTMLElement): void {
|
|
267
288
|
const state = states.get(element);
|
|
268
289
|
|
|
269
290
|
if (state == null) {
|
|
@@ -338,7 +359,7 @@ on(document, 'focusin', onFocusin);
|
|
|
338
359
|
on(document, 'focusout', onFocusout);
|
|
339
360
|
on(document, 'pointermove', onPointermove);
|
|
340
361
|
|
|
341
|
-
attributable(ATTRIBUTE,
|
|
362
|
+
attributable(ATTRIBUTE, addTooltip, removeTooltip);
|
|
342
363
|
|
|
343
364
|
// #endregion
|
|
344
365
|
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
//#region src/js/focus-trap/embedded.d.ts
|
|
2
|
-
declare class FocusTrap {
|
|
3
|
-
disabled: boolean;
|
|
4
|
-
connected: boolean;
|
|
5
|
-
element: HTMLElement;
|
|
6
|
-
embedded: boolean;
|
|
7
|
-
get contain(): boolean;
|
|
8
|
-
get noescape(): boolean;
|
|
9
|
-
constructor(element: HTMLElement, options?: Options);
|
|
10
|
-
connect(): void;
|
|
11
|
-
destroy(): void;
|
|
12
|
-
disable(): void;
|
|
13
|
-
disconnect(): void;
|
|
14
|
-
enable(): void;
|
|
15
|
-
focus(last: boolean): void;
|
|
16
|
-
}
|
|
17
|
-
type Options = {
|
|
18
|
-
noescape: boolean;
|
|
19
|
-
};
|
|
20
|
-
declare function createFocusTrap(element: HTMLElement, options?: Options): FocusTrap;
|
|
21
|
-
declare function removeFocusTrap(element: HTMLElement): void;
|
|
22
|
-
declare const FOCUS_TRAP_SELECTOR = "oui-focus-trap";
|
|
23
|
-
declare const FOCUS_TRAP_CONTAIN = "oui-focus-trap-contain";
|
|
24
|
-
declare const FOCUS_TRAP_NOESCAPE = "oui-focus-trap-noescape";
|
|
25
|
-
//#endregion
|
|
26
|
-
export { createFocusTrap as a, FocusTrap as i, FOCUS_TRAP_NOESCAPE as n, removeFocusTrap as o, FOCUS_TRAP_SELECTOR as r, FOCUS_TRAP_CONTAIN as t };
|