@oscarpalmer/oui 0.26.1 → 0.28.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/floatable.css +9 -2
- package/dist/css/message.css +1 -0
- package/dist/css/reset.css +1 -0
- package/dist/js/dialog.d.mts +4 -0
- package/dist/js/dialog.mjs +33 -1
- package/dist/js/draggable-zszaW1qS.d.mts +155 -0
- package/dist/js/floatable/floatable.embedded.d.mts +2 -0
- package/dist/js/floatable/{embedded.mjs → floatable.embedded.mjs} +16 -11
- package/dist/js/floatable/floatable.standalone.d.mts +2 -0
- package/dist/js/floatable/floatable.standalone.mjs +2 -0
- package/dist/js/{embedded-CSOh5njI.d.mts → floatable.embedded-YxGwzgbE.d.mts} +5 -2
- package/dist/js/focus-trap/focus-trap.embedded.d.mts +2 -0
- package/dist/js/focus-trap/{embedded.mjs → focus-trap.embedded.mjs} +17 -15
- package/dist/js/focus-trap/focus-trap.standalone.d.mts +2 -0
- package/dist/js/focus-trap/focus-trap.standalone.mjs +2 -0
- package/dist/js/{embedded-CHK82Uh5.d.mts → focus-trap.embedded-DR8NpZfr.d.mts} +18 -18
- package/dist/js/index.d.mts +5 -3
- package/dist/js/index.mjs +11 -7
- package/dist/js/internal/draggable.d.mts +2 -2
- package/dist/js/internal/draggable.mjs +147 -95
- package/dist/js/movable/movable.embedded.d.mts +2 -0
- package/dist/js/movable/movable.embedded.mjs +184 -0
- package/dist/js/movable/movable.standalone.d.mts +2 -0
- package/dist/js/movable/movable.standalone.mjs +2 -0
- package/dist/js/popover.d.mts +2 -2
- package/dist/js/popover.mjs +4 -3
- package/dist/js/resizable/resizable.embedded.d.mts +2 -0
- package/dist/js/resizable/resizable.embedded.mjs +186 -0
- package/dist/js/resizable/resizable.standalone.d.mts +2 -0
- package/dist/js/resizable/resizable.standalone.mjs +2 -0
- package/dist/js/resizable.embedded-CXrUVPzR.d.mts +35 -0
- package/dist/js/sortable/sortable.embedded.d.mts +2 -0
- package/dist/js/{sortable.mjs → sortable/sortable.embedded.mjs} +88 -63
- package/dist/js/sortable/sortable.standalone.d.mts +2 -0
- package/dist/js/sortable/sortable.standalone.mjs +2 -0
- package/dist/js/splitter.mjs +6 -6
- package/dist/js/toolbar.mjs +6 -6
- package/dist/js/tooltip.d.mts +2 -2
- package/dist/js/tooltip.mjs +36 -33
- package/package.json +15 -11
- package/src/css/floatable.scss +9 -2
- package/src/css/form.scss +6 -6
- package/src/css/message.scss +1 -0
- package/src/css/reset.scss +1 -0
- package/src/js/dialog.ts +61 -1
- package/src/js/floatable/{embedded.ts → floatable.embedded.ts} +18 -10
- package/src/js/floatable/floatable.standalone.ts +6 -0
- package/src/js/focus-trap/{embedded.ts → focus-trap.embedded.ts} +20 -17
- package/src/js/focus-trap/focus-trap.standalone.ts +1 -0
- package/src/js/index.ts +5 -4
- package/src/js/internal/draggable.ts +310 -169
- package/src/js/movable/movable.embedded.ts +328 -0
- package/src/js/movable/movable.standalone.ts +2 -0
- package/src/js/popover.ts +3 -2
- package/src/js/resizable/resizable.embedded.ts +383 -0
- package/src/js/resizable/resizable.standalone.ts +5 -0
- package/src/js/{sortable.ts → sortable/sortable.embedded.ts} +170 -103
- package/src/js/sortable/sortable.standalone.ts +2 -0
- package/src/js/splitter.ts +1 -1
- package/src/js/tooltip.ts +49 -43
- package/dist/js/floatable/embedded.d.mts +0 -2
- package/dist/js/floatable/standalone.d.mts +0 -2
- package/dist/js/floatable/standalone.mjs +0 -2
- package/dist/js/focus-trap/embedded.d.mts +0 -2
- package/dist/js/focus-trap/standalone.d.mts +0 -2
- package/dist/js/focus-trap/standalone.mjs +0 -6
- package/dist/js/movable-dNqBctx3.d.mts +0 -101
- package/dist/js/movable.d.mts +0 -2
- package/dist/js/movable.mjs +0 -152
- package/dist/js/sortable.d.mts +0 -2
- package/src/js/floatable/standalone.ts +0 -1
- package/src/js/focus-trap/standalone.ts +0 -6
- package/src/js/movable.ts +0 -260
package/dist/js/tooltip.mjs
CHANGED
|
@@ -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,7 @@ var OuiTooltip = class {
|
|
|
12
12
|
* Is the tooltip open?
|
|
13
13
|
*/
|
|
14
14
|
get open() {
|
|
15
|
-
return this
|
|
15
|
+
return this === activeTooltip && content.checkVisibility();
|
|
16
16
|
}
|
|
17
17
|
/**
|
|
18
18
|
* Open or close the tooltip
|
|
@@ -39,52 +39,50 @@ var OuiTooltip = class {
|
|
|
39
39
|
}
|
|
40
40
|
};
|
|
41
41
|
function activate(event, focus) {
|
|
42
|
-
const
|
|
43
|
-
const
|
|
44
|
-
if (
|
|
42
|
+
const targetAnchor = findAncestor(event, SELECTOR);
|
|
43
|
+
const targetContent = findAncestor(event, SELECTOR_CONTENT);
|
|
44
|
+
if (targetAnchor == null && targetContent == null) {
|
|
45
45
|
if (!focused) {
|
|
46
|
-
closeTooltip(activeState);
|
|
46
|
+
closeTooltip(true, activeState);
|
|
47
47
|
activeState = void 0;
|
|
48
48
|
activeTooltip = void 0;
|
|
49
49
|
}
|
|
50
50
|
return;
|
|
51
51
|
}
|
|
52
|
-
const state = states.get(
|
|
52
|
+
const state = states.get(targetAnchor ?? targetContent);
|
|
53
53
|
if (state == null || state.tooltip === activeTooltip) return;
|
|
54
54
|
const shouldDelay = activeTooltip == null;
|
|
55
|
-
closeTooltip(activeState);
|
|
55
|
+
closeTooltip(false, activeState);
|
|
56
56
|
activeState = state;
|
|
57
57
|
activeTooltip = state.tooltip;
|
|
58
58
|
focused = focus;
|
|
59
59
|
state.timer = setTimeout(() => {
|
|
60
|
-
|
|
60
|
+
content.innerHTML = "";
|
|
61
|
+
content.append(...state.elements);
|
|
62
|
+
state.floatable.show();
|
|
61
63
|
setTimeout(() => {
|
|
62
64
|
state.floatable.update();
|
|
63
|
-
state.content.setAttribute(ATTRIBUTE_CONTENT_OPEN, "");
|
|
64
65
|
});
|
|
65
66
|
}, shouldDelay ? delay : 0);
|
|
66
67
|
}
|
|
67
68
|
function addTooltip(element) {
|
|
68
69
|
if (!states.has(element)) createTooltip(element);
|
|
69
70
|
}
|
|
70
|
-
function closeTooltip(state) {
|
|
71
|
+
function closeTooltip(clear, state) {
|
|
71
72
|
if (state == null) return;
|
|
72
73
|
if (state.timer != null) clearTimeout(state.timer);
|
|
73
|
-
|
|
74
|
-
state.content?.removeAttribute(ATTRIBUTE_CONTENT_OPEN);
|
|
74
|
+
if (clear) content?.hidePopover();
|
|
75
75
|
}
|
|
76
76
|
function createTooltip(anchor) {
|
|
77
|
-
const
|
|
78
|
-
if (
|
|
79
|
-
|
|
80
|
-
states.set(anchor, getState(anchor, content));
|
|
77
|
+
const elements = getElements(anchor);
|
|
78
|
+
if (elements == null) return;
|
|
79
|
+
states.set(anchor, getState(anchor, elements));
|
|
81
80
|
}
|
|
82
81
|
function destroyTooltip(state) {
|
|
83
82
|
clearTimeout(state.timer);
|
|
84
|
-
state.content.remove();
|
|
85
83
|
state.floatable.destroy();
|
|
86
84
|
state.anchor = void 0;
|
|
87
|
-
state.
|
|
85
|
+
state.elements = void 0;
|
|
88
86
|
state.floatable = void 0;
|
|
89
87
|
state.tooltip = void 0;
|
|
90
88
|
}
|
|
@@ -102,34 +100,31 @@ function findElements(attribute) {
|
|
|
102
100
|
}
|
|
103
101
|
return elements;
|
|
104
102
|
}
|
|
105
|
-
function getContent(
|
|
106
|
-
const elements = getElements(anchor);
|
|
107
|
-
if (elements == null) return;
|
|
103
|
+
function getContent() {
|
|
108
104
|
const content = createElement(CONTENT_TAGNAME, {
|
|
109
105
|
popover: "hint",
|
|
110
106
|
role: "tooltip",
|
|
111
107
|
tabIndex: -1
|
|
112
108
|
});
|
|
113
109
|
content.setAttribute(ATTRIBUTE_CONTENT, "");
|
|
114
|
-
content.append(...elements);
|
|
115
110
|
return content;
|
|
116
111
|
}
|
|
117
112
|
function getElements(anchor) {
|
|
118
113
|
let elements = findElements(anchor.getAttribute(ARIA_LABELLEDBY));
|
|
119
|
-
if (elements.length > 0) return elements
|
|
114
|
+
if (elements.length > 0) return elements;
|
|
120
115
|
elements = findElements(anchor.getAttribute(ARIA_DESCRIBEDBY));
|
|
121
|
-
if (elements.length > 0) return elements
|
|
122
|
-
let
|
|
123
|
-
if (isNullableOrWhitespace(
|
|
124
|
-
if (isNullableOrWhitespace(
|
|
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;
|
|
125
120
|
const paragraph = document.createElement("p");
|
|
126
|
-
paragraph.textContent =
|
|
121
|
+
paragraph.textContent = text;
|
|
127
122
|
return [paragraph];
|
|
128
123
|
}
|
|
129
|
-
function getState(anchor,
|
|
124
|
+
function getState(anchor, elements) {
|
|
130
125
|
const state = {
|
|
131
126
|
anchor,
|
|
132
|
-
|
|
127
|
+
elements,
|
|
133
128
|
floatable: createEmbeddedFloatable(anchor, content, options),
|
|
134
129
|
tooltip: null
|
|
135
130
|
};
|
|
@@ -153,7 +148,7 @@ function onFocusout() {
|
|
|
153
148
|
if (focusTimer != null) clearTimeout(focusTimer);
|
|
154
149
|
focusTimer = setTimeout(() => {
|
|
155
150
|
if (activeTooltip == null || document.activeElement == null || findAncestor(document.activeElement, SELECTOR) != null) return;
|
|
156
|
-
closeTooltip(activeState);
|
|
151
|
+
closeTooltip(true, activeState);
|
|
157
152
|
activeState = void 0;
|
|
158
153
|
activeTooltip = void 0;
|
|
159
154
|
});
|
|
@@ -161,6 +156,10 @@ function onFocusout() {
|
|
|
161
156
|
function onPointermove(event) {
|
|
162
157
|
activate(event, false);
|
|
163
158
|
}
|
|
159
|
+
function onToggle(event) {
|
|
160
|
+
if (event.newState === "open") content.setAttribute(ATTRIBUTE_CONTENT_OPEN, "");
|
|
161
|
+
else content.removeAttribute(ATTRIBUTE_CONTENT_OPEN);
|
|
162
|
+
}
|
|
164
163
|
function removeTooltip(element) {
|
|
165
164
|
const state = states.get(element);
|
|
166
165
|
if (state == null) return;
|
|
@@ -187,16 +186,20 @@ const ATTRIBUTE_CONTENT = `${ATTRIBUTE}-content`;
|
|
|
187
186
|
const ATTRIBUTE_CONTENT_OPEN = `${ATTRIBUTE_CONTENT}-open`;
|
|
188
187
|
const SELECTOR = `[${ATTRIBUTE}]`;
|
|
189
188
|
const SELECTOR_CONTENT = `[${ATTRIBUTE_CONTENT}]`;
|
|
189
|
+
const content = getContent();
|
|
190
190
|
const states = /* @__PURE__ */ new WeakMap();
|
|
191
191
|
const options = {
|
|
192
192
|
attribute: `${ATTRIBUTE}-position`,
|
|
193
|
-
position: "above"
|
|
193
|
+
position: "above",
|
|
194
|
+
reusable: true
|
|
194
195
|
};
|
|
195
196
|
let delay = 500;
|
|
196
197
|
let focused = false;
|
|
197
198
|
let activeState;
|
|
198
199
|
let activeTooltip;
|
|
199
200
|
let focusTimer;
|
|
201
|
+
document.body.append(content);
|
|
202
|
+
on(content, "toggle", onToggle);
|
|
200
203
|
on(document, "focusin", onFocusin);
|
|
201
204
|
on(document, "focusout", onFocusout);
|
|
202
205
|
on(document, "pointermove", onPointermove);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@oscarpalmer/oui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.28.0",
|
|
4
4
|
"description": "Styles, components, and utilities for building websites and applications.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"css",
|
|
@@ -38,24 +38,28 @@
|
|
|
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
|
+
"./resizable": {
|
|
57
|
+
"types": "./dist/js/resizable/resizable.standalone.d.mts",
|
|
58
|
+
"default": "./dist/js/resizable/resizable.standalone.mjs"
|
|
59
|
+
},
|
|
56
60
|
"./sortable": {
|
|
57
|
-
"types": "./dist/js/sortable.d.mts",
|
|
58
|
-
"default": "./dist/js/sortable.mjs"
|
|
61
|
+
"types": "./dist/js/sortable/sortable.standalone.d.mts",
|
|
62
|
+
"default": "./dist/js/sortable/sortable.standalone.mjs"
|
|
59
63
|
},
|
|
60
64
|
"./splitter": {
|
|
61
65
|
"types": "./dist/js/splitter.d.mts",
|
|
@@ -86,7 +90,7 @@
|
|
|
86
90
|
"@oscarpalmer/toretto": "^0.46"
|
|
87
91
|
},
|
|
88
92
|
"devDependencies": {
|
|
89
|
-
"@oxlint/plugins": "^1.
|
|
93
|
+
"@oxlint/plugins": "^1.72",
|
|
90
94
|
"@vitest/coverage-istanbul": "^4.1",
|
|
91
95
|
"autoprefixer": "^10.5",
|
|
92
96
|
"concurrently": "^10",
|
|
@@ -95,7 +99,7 @@
|
|
|
95
99
|
"postcss-input-range": "^6",
|
|
96
100
|
"postcss-scss": "^4",
|
|
97
101
|
"sass": "^1.101",
|
|
98
|
-
"stylelint": "^17.
|
|
102
|
+
"stylelint": "^17.14",
|
|
99
103
|
"stylelint-config-standard-scss": "^17",
|
|
100
104
|
"tsdown": "^0.22",
|
|
101
105
|
"typescript": "^6",
|
package/src/css/floatable.scss
CHANGED
|
@@ -36,6 +36,15 @@
|
|
|
36
36
|
transition-delay: 0.125s;
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
+
:where([oui-dialog]) {
|
|
40
|
+
position: relative;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
:where([oui-dialog-resizer]) {
|
|
44
|
+
position: absolute;
|
|
45
|
+
inset: auto 0 0 auto;
|
|
46
|
+
}
|
|
47
|
+
|
|
39
48
|
:where([oui-popover], [oui-tooltip-content]) {
|
|
40
49
|
@include mixins.floatable;
|
|
41
50
|
}
|
|
@@ -77,8 +86,6 @@
|
|
|
77
86
|
:where([oui-tooltip-content]) {
|
|
78
87
|
max-width: 48ch;
|
|
79
88
|
padding: 0.75em 1em;
|
|
80
|
-
overflow: visible;
|
|
81
|
-
opacity: 0;
|
|
82
89
|
background-color: var(--oui-absolute);
|
|
83
90
|
border-radius: 0.25rem;
|
|
84
91
|
font-size: 0.875rem;
|
package/src/css/form.scss
CHANGED
|
@@ -173,9 +173,9 @@
|
|
|
173
173
|
|
|
174
174
|
:where(
|
|
175
175
|
&.oui-validate:not([disabled], [readonly], [aria-disabled='true' i]):is(
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
176
|
+
:user-invalid:not([aria-invalid='false' i]),
|
|
177
|
+
[aria-invalid='true' i]
|
|
178
|
+
)
|
|
179
179
|
) {
|
|
180
180
|
@include invalid;
|
|
181
181
|
|
|
@@ -201,9 +201,9 @@
|
|
|
201
201
|
|
|
202
202
|
:where(
|
|
203
203
|
&.oui-validate:not([disabled], [readonly], [aria-disabled='true' i]):is(
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
204
|
+
:user-valid:not([aria-invalid='true' i]),
|
|
205
|
+
[aria-invalid='false' i]
|
|
206
|
+
)
|
|
207
207
|
) {
|
|
208
208
|
@include valid;
|
|
209
209
|
|
package/src/css/message.scss
CHANGED
package/src/css/reset.scss
CHANGED
package/src/js/dialog.ts
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
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';
|
|
7
|
+
import {createResizable, RESIZABLE_ATTRIBUTE_HANDLE} from './resizable/resizable.embedded';
|
|
8
|
+
import type {OuiResizable} from './resizable/resizable.standalone';
|
|
5
9
|
|
|
6
10
|
// #region Types
|
|
7
11
|
|
|
@@ -79,6 +83,8 @@ type OuiDialogState = {
|
|
|
79
83
|
element: HTMLDialogElement;
|
|
80
84
|
listeners: RemovableEventListener[];
|
|
81
85
|
modal: boolean;
|
|
86
|
+
movable?: OuiMovable;
|
|
87
|
+
resizable?: OuiResizable;
|
|
82
88
|
};
|
|
83
89
|
|
|
84
90
|
// #endregion
|
|
@@ -105,9 +111,14 @@ function destroyDialog(state: OuiDialogState): void {
|
|
|
105
111
|
listener();
|
|
106
112
|
}
|
|
107
113
|
|
|
114
|
+
state.movable?.destroy();
|
|
115
|
+
state.resizable?.destroy();
|
|
116
|
+
|
|
108
117
|
state.dialog = undefined as never;
|
|
109
118
|
state.element = undefined as never;
|
|
110
119
|
state.listeners = undefined as never;
|
|
120
|
+
state.movable = undefined;
|
|
121
|
+
state.resizable = undefined;
|
|
111
122
|
}
|
|
112
123
|
|
|
113
124
|
/**
|
|
@@ -120,6 +131,34 @@ function getDialog(element: HTMLDialogElement): OuiDialog | undefined {
|
|
|
120
131
|
return states.get(element)?.dialog;
|
|
121
132
|
}
|
|
122
133
|
|
|
134
|
+
function getMovable(element: HTMLDialogElement): OuiMovable | undefined {
|
|
135
|
+
const handles = element.querySelectorAll(SELECTOR_MOVER);
|
|
136
|
+
|
|
137
|
+
if (handles.length === 0) {
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
for (const handle of handles) {
|
|
142
|
+
handle.setAttribute(ATTRIBUTE_MOVABLE_HANDLE, '');
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return createMovable(element, {
|
|
146
|
+
container: element.getAttribute(ATTRIBUTE_CONTAINER) ?? undefined,
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function getResizable(element: HTMLDialogElement): OuiResizable | undefined {
|
|
151
|
+
const handle = element.querySelector(SELECTOR_RESIZER);
|
|
152
|
+
|
|
153
|
+
if (handle == null) {
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
handle.setAttribute(RESIZABLE_ATTRIBUTE_HANDLE, '');
|
|
158
|
+
|
|
159
|
+
return createResizable(element);
|
|
160
|
+
}
|
|
161
|
+
|
|
123
162
|
function getState(element: HTMLDialogElement): OuiDialogState {
|
|
124
163
|
const state: OuiDialogState = {
|
|
125
164
|
element,
|
|
@@ -136,9 +175,21 @@ function getState(element: HTMLDialogElement): OuiDialogState {
|
|
|
136
175
|
|
|
137
176
|
element.setAttribute(ARIA_MODAL, String(modal));
|
|
138
177
|
element.setAttribute(ATTRIBUTE_OPEN, '');
|
|
178
|
+
|
|
179
|
+
element.focus();
|
|
180
|
+
|
|
181
|
+
setTimeout(() => {
|
|
182
|
+
state.resizable?.set();
|
|
183
|
+
}, 500);
|
|
139
184
|
} else {
|
|
140
185
|
element.removeAttribute(ATTRIBUTE_OPEN);
|
|
141
186
|
element.setAttribute(ARIA_MODAL, String(state.modal));
|
|
187
|
+
|
|
188
|
+
element.style.inset = '';
|
|
189
|
+
element.style.position = '';
|
|
190
|
+
element.style.transform = '';
|
|
191
|
+
|
|
192
|
+
state.resizable?.reset();
|
|
142
193
|
}
|
|
143
194
|
}),
|
|
144
195
|
],
|
|
@@ -147,6 +198,9 @@ function getState(element: HTMLDialogElement): OuiDialogState {
|
|
|
147
198
|
|
|
148
199
|
state.dialog = new OuiDialog(state);
|
|
149
200
|
|
|
201
|
+
state.movable = getMovable(element);
|
|
202
|
+
state.resizable = getResizable(element);
|
|
203
|
+
|
|
150
204
|
return state;
|
|
151
205
|
}
|
|
152
206
|
|
|
@@ -170,6 +224,8 @@ const ARIA_MODAL = 'aria-modal';
|
|
|
170
224
|
|
|
171
225
|
const ATTRIBUTE = 'oui-dialog';
|
|
172
226
|
|
|
227
|
+
const ATTRIBUTE_CONTAINER = `${ATTRIBUTE}-container`;
|
|
228
|
+
|
|
173
229
|
const ATTRIBUTE_OPEN = `${ATTRIBUTE}-open`;
|
|
174
230
|
|
|
175
231
|
const COMMAND_SHOW_MODAL = 'show-modal';
|
|
@@ -178,8 +234,12 @@ const EVENT_TOGGLE = 'toggle';
|
|
|
178
234
|
|
|
179
235
|
const MESSAGE = 'The element must be an instance of HTMLDialogElement';
|
|
180
236
|
|
|
237
|
+
const SELECTOR_MOVER = `[${ATTRIBUTE}-mover]`;
|
|
238
|
+
|
|
181
239
|
const SELECTOR_MODAL = ':modal';
|
|
182
240
|
|
|
241
|
+
const SELECTOR_RESIZER = `[${ATTRIBUTE}-resizer]`;
|
|
242
|
+
|
|
183
243
|
const TRUE = 'true';
|
|
184
244
|
|
|
185
245
|
const states = new WeakMap<HTMLElement, OuiDialogState>();
|
|
@@ -5,7 +5,7 @@ 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
10
|
* Open the floatable when created? _(defaults to `true`)_
|
|
11
11
|
*/
|
|
@@ -50,10 +50,8 @@ export class OuiFloatable {
|
|
|
50
50
|
content.popover = 'manual';
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
-
|
|
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
|
/**
|
|
@@ -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
|
-
|
|
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 ===
|
|
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>();
|
|
@@ -4,6 +4,7 @@ import {on} from '@oscarpalmer/toretto/event';
|
|
|
4
4
|
import {findAncestor} from '@oscarpalmer/toretto/find';
|
|
5
5
|
import {getFocusable} from '@oscarpalmer/toretto/focusable';
|
|
6
6
|
import {isHTMLOrSVGElement} from '@oscarpalmer/toretto/is';
|
|
7
|
+
import {attributable} from '../internal/attributable';
|
|
7
8
|
|
|
8
9
|
// #region Types
|
|
9
10
|
|
|
@@ -14,14 +15,14 @@ export class OuiFocusTrap {
|
|
|
14
15
|
readonly #state: OuiFocusTrapState;
|
|
15
16
|
|
|
16
17
|
/**
|
|
17
|
-
* Is focused maintained within the
|
|
18
|
+
* Is focused maintained within the _focus trap_ when clicking outside of it?
|
|
18
19
|
*/
|
|
19
20
|
get contain(): boolean {
|
|
20
21
|
return this.#state.element.hasAttribute(ATTRIBUTE_FOCUSTRAP_CONTAIN);
|
|
21
22
|
}
|
|
22
23
|
|
|
23
24
|
/**
|
|
24
|
-
* Should focus be maintained within the
|
|
25
|
+
* Should focus be maintained within the _focus trap_ when clicking outside of it?
|
|
25
26
|
*/
|
|
26
27
|
set contain(value: boolean) {
|
|
27
28
|
if (value === true) {
|
|
@@ -32,14 +33,14 @@ export class OuiFocusTrap {
|
|
|
32
33
|
}
|
|
33
34
|
|
|
34
35
|
/**
|
|
35
|
-
* Is the
|
|
36
|
+
* Is the _focus trap_ disabled?
|
|
36
37
|
*/
|
|
37
38
|
get disabled(): boolean {
|
|
38
39
|
return this.#state.disabled;
|
|
39
40
|
}
|
|
40
41
|
|
|
41
42
|
/**
|
|
42
|
-
* Should the
|
|
43
|
+
* Should the _focus trap_ be disabled?
|
|
43
44
|
*/
|
|
44
45
|
set disabled(value: boolean) {
|
|
45
46
|
if (value === true) {
|
|
@@ -50,14 +51,14 @@ export class OuiFocusTrap {
|
|
|
50
51
|
}
|
|
51
52
|
|
|
52
53
|
/**
|
|
53
|
-
* Is focus allowed to escape when pressing _Escape_ within the
|
|
54
|
+
* Is focus allowed to escape when pressing _Escape_ within the _focus trap_?
|
|
54
55
|
*/
|
|
55
56
|
get noescape(): boolean {
|
|
56
57
|
return this.#state.element.hasAttribute(ATTRIBUTE_FOCUSTRAP_NOESCAPE);
|
|
57
58
|
}
|
|
58
59
|
|
|
59
60
|
/**
|
|
60
|
-
* Should the
|
|
61
|
+
* Should the _focus trap_ allow focus to escape when pressing _Escape_?
|
|
61
62
|
*/
|
|
62
63
|
set noescape(value: boolean) {
|
|
63
64
|
if (value === true) {
|
|
@@ -82,7 +83,7 @@ export class OuiFocusTrap {
|
|
|
82
83
|
}
|
|
83
84
|
|
|
84
85
|
/**
|
|
85
|
-
* Destroys the
|
|
86
|
+
* Destroys the _focus trap_
|
|
86
87
|
*/
|
|
87
88
|
destroy(): void {
|
|
88
89
|
if (this.#destroyed) {
|
|
@@ -105,7 +106,7 @@ export class OuiFocusTrap {
|
|
|
105
106
|
}
|
|
106
107
|
|
|
107
108
|
/**
|
|
108
|
-
* Disables the
|
|
109
|
+
* Disables the _focus trap_
|
|
109
110
|
*/
|
|
110
111
|
disable(): void {
|
|
111
112
|
if (!this.#destroyed && !(this.disabled || this.#state.options.noescape)) {
|
|
@@ -114,7 +115,7 @@ export class OuiFocusTrap {
|
|
|
114
115
|
}
|
|
115
116
|
|
|
116
117
|
/**
|
|
117
|
-
* Enables the
|
|
118
|
+
* Enables the _focus trap_
|
|
118
119
|
*/
|
|
119
120
|
enable(): void {
|
|
120
121
|
if (!this.#destroyed && this.disabled) {
|
|
@@ -123,9 +124,9 @@ export class OuiFocusTrap {
|
|
|
123
124
|
}
|
|
124
125
|
|
|
125
126
|
/**
|
|
126
|
-
* Focuses the
|
|
127
|
+
* Focuses the _focus trap_
|
|
127
128
|
*
|
|
128
|
-
* @param last When `true`, focuses the last focusable element within the
|
|
129
|
+
* @param last When `true`, focuses the last focusable element within the _focus trap_ instead of the first
|
|
129
130
|
*/
|
|
130
131
|
focus(last: boolean): void {
|
|
131
132
|
if (!this.#destroyed && !this.disabled) {
|
|
@@ -134,13 +135,13 @@ export class OuiFocusTrap {
|
|
|
134
135
|
}
|
|
135
136
|
}
|
|
136
137
|
|
|
137
|
-
type OuiFocusTrapOptions = {
|
|
138
|
+
export type OuiFocusTrapOptions = {
|
|
138
139
|
/**
|
|
139
|
-
* Should focus be maintained within the
|
|
140
|
+
* Should focus be maintained within the _focus trap_ when clicking outside of it?
|
|
140
141
|
*/
|
|
141
142
|
contain?: boolean;
|
|
142
143
|
/**
|
|
143
|
-
* Should the
|
|
144
|
+
* Should the _focus trap_ allow focus to escape when pressing _Escape_?
|
|
144
145
|
*/
|
|
145
146
|
noescape?: boolean;
|
|
146
147
|
};
|
|
@@ -156,11 +157,11 @@ type OuiFocusTrapState = {
|
|
|
156
157
|
// #region Functions
|
|
157
158
|
|
|
158
159
|
/**
|
|
159
|
-
* Creates _(or retrieves)_ a
|
|
160
|
+
* Creates _(or retrieves)_ a _focus trap_ for an element
|
|
160
161
|
*
|
|
161
162
|
* @param element Element to trap focus within
|
|
162
|
-
* @param options
|
|
163
|
-
* @returns
|
|
163
|
+
* @param options _Focus trap_ options
|
|
164
|
+
* @returns _Focus trap_ instance
|
|
164
165
|
*/
|
|
165
166
|
export function createFocusTrap(element: HTMLElement, options?: OuiFocusTrapOptions): OuiFocusTrap {
|
|
166
167
|
if (!isHTMLOrSVGElement(element)) {
|
|
@@ -399,6 +400,8 @@ let lastTarget: HTMLElement | undefined;
|
|
|
399
400
|
|
|
400
401
|
// #region Initialization
|
|
401
402
|
|
|
403
|
+
attributable(ATTRIBUTE_FOCUSTRAP, createFocusTrap, removeFocusTrap);
|
|
404
|
+
|
|
402
405
|
on(document, 'focusin', onFocusIn, EVENT_OPTIONS);
|
|
403
406
|
on(document, 'keydown', onKeydown, EVENT_OPTIONS);
|
|
404
407
|
on(document, 'mousedown', onPointerdown, EVENT_OPTIONS);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {createFocusTrap, type OuiFocusTrap, type OuiFocusTrapOptions} from './focus-trap.embedded';
|