@oscarpalmer/oui 0.23.0 → 0.24.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/js/floatable.d.mts +59 -0
- package/dist/js/floatable.mjs +130 -0
- package/dist/js/index.d.mts +2 -1
- package/dist/js/index.mjs +2 -1
- package/dist/js/internal/attributable.mjs +4 -3
- package/dist/js/popover.mjs +9 -5
- package/dist/js/sortable.mjs +7 -5
- package/dist/js/splitter.mjs +17 -13
- package/dist/js/tooltip.d.mts +6 -0
- package/dist/js/tooltip.mjs +14 -7
- package/package.json +1 -1
- package/src/js/floatable.ts +237 -0
- package/src/js/index.ts +2 -1
- package/src/js/internal/attributable.ts +5 -3
- package/src/js/popover.ts +23 -7
- package/src/js/sortable.ts +8 -5
- package/src/js/splitter.ts +26 -9
- package/src/js/tooltip.ts +14 -7
- package/dist/js/internal/floatable.d.mts +0 -18
- package/dist/js/internal/floatable.mjs +0 -67
- package/src/js/internal/floatable.ts +0 -118
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
//#region src/js/floatable.d.ts
|
|
2
|
+
declare class Floatable {
|
|
3
|
+
#private;
|
|
4
|
+
anchor: HTMLElement;
|
|
5
|
+
content: HTMLElement;
|
|
6
|
+
options: FloatableOptions;
|
|
7
|
+
/**
|
|
8
|
+
* Is the _Floatable_ open?
|
|
9
|
+
*/
|
|
10
|
+
get open(): boolean;
|
|
11
|
+
constructor(anchor: HTMLElement, content: HTMLElement, options: FloatableOptions, standalone: boolean);
|
|
12
|
+
/**
|
|
13
|
+
* Destroy the floatable, removing it from the DOM and cleaning up references
|
|
14
|
+
*/
|
|
15
|
+
destroy(): void;
|
|
16
|
+
/**
|
|
17
|
+
* Hide the floatable
|
|
18
|
+
*/
|
|
19
|
+
hide(): void;
|
|
20
|
+
/**
|
|
21
|
+
* Show the floatable
|
|
22
|
+
*/
|
|
23
|
+
show(): void;
|
|
24
|
+
/**
|
|
25
|
+
* Update the position of the floatable, with an optional new position
|
|
26
|
+
*
|
|
27
|
+
* @param position Optional new position
|
|
28
|
+
*/
|
|
29
|
+
update(position?: FloatablePosition): void;
|
|
30
|
+
}
|
|
31
|
+
type FloatableOptions = {
|
|
32
|
+
attribute?: string;
|
|
33
|
+
position: FloatablePosition;
|
|
34
|
+
};
|
|
35
|
+
type FloatablePosition = 'above' | 'above-end' | 'above-start' | 'below' | 'below-end' | 'below-start' | 'end' | 'end-bottom' | 'end-top' | 'start' | 'start-bottom' | 'start-top';
|
|
36
|
+
type GetFloatableOptions = {
|
|
37
|
+
/**
|
|
38
|
+
* Open the _Floatable_ when created? _(defaults to `true`)_
|
|
39
|
+
*/
|
|
40
|
+
open?: boolean;
|
|
41
|
+
/**
|
|
42
|
+
* Position of the _Floatable_ _(defaults to `below`)_
|
|
43
|
+
*/
|
|
44
|
+
position?: FloatablePosition;
|
|
45
|
+
};
|
|
46
|
+
declare function createFloatable(anchor: HTMLElement, content: HTMLElement, options: FloatableOptions, standalone: boolean): Floatable;
|
|
47
|
+
/**
|
|
48
|
+
* Create a _Floatable_ for an anchor and content element
|
|
49
|
+
*
|
|
50
|
+
* @param anchor Anchor element
|
|
51
|
+
* @param content Content element
|
|
52
|
+
* @param options _Floatable_ options
|
|
53
|
+
* @returns _Floatable_ instance
|
|
54
|
+
*/
|
|
55
|
+
declare function getFloatable(anchor: HTMLElement | SVGElement, content: HTMLElement | SVGElement, options?: GetFloatableOptions): Floatable;
|
|
56
|
+
declare function removeFloatable(content: HTMLElement): void;
|
|
57
|
+
declare const ATTRIBUTE_FLOATABLE = "oui-floatable";
|
|
58
|
+
//#endregion
|
|
59
|
+
export { ATTRIBUTE_FLOATABLE, Floatable, FloatableOptions, FloatablePosition, createFloatable, getFloatable, removeFloatable };
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { isPlainObject } from "@oscarpalmer/atoms/is";
|
|
2
|
+
import { isHTMLOrSVGElement } from "@oscarpalmer/toretto/is";
|
|
3
|
+
//#region src/js/floatable.ts
|
|
4
|
+
var Floatable = class {
|
|
5
|
+
anchor;
|
|
6
|
+
content;
|
|
7
|
+
options;
|
|
8
|
+
#destroyed = false;
|
|
9
|
+
/**
|
|
10
|
+
* Is the _Floatable_ open?
|
|
11
|
+
*/
|
|
12
|
+
get open() {
|
|
13
|
+
return this.content.checkVisibility();
|
|
14
|
+
}
|
|
15
|
+
constructor(anchor, content, options, standalone) {
|
|
16
|
+
this.anchor = anchor;
|
|
17
|
+
this.content = content;
|
|
18
|
+
this.options = options;
|
|
19
|
+
if (standalone) {
|
|
20
|
+
content.setAttribute(ATTRIBUTE_FLOATABLE, "");
|
|
21
|
+
content.popover = "manual";
|
|
22
|
+
}
|
|
23
|
+
const anchorName = getAnchorName(anchor);
|
|
24
|
+
anchor.style.anchorName = anchorName;
|
|
25
|
+
content.style.positionAnchor = anchorName;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Destroy the floatable, removing it from the DOM and cleaning up references
|
|
29
|
+
*/
|
|
30
|
+
destroy() {
|
|
31
|
+
if (this.#destroyed) return;
|
|
32
|
+
this.#destroyed = true;
|
|
33
|
+
removeFloatable(this.content);
|
|
34
|
+
this.anchor = null;
|
|
35
|
+
this.content = null;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Hide the floatable
|
|
39
|
+
*/
|
|
40
|
+
hide() {
|
|
41
|
+
if (!this.#destroyed) this.content.hidePopover();
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Show the floatable
|
|
45
|
+
*/
|
|
46
|
+
show() {
|
|
47
|
+
if (!this.#destroyed) this.content.showPopover();
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Update the position of the floatable, with an optional new position
|
|
51
|
+
*
|
|
52
|
+
* @param position Optional new position
|
|
53
|
+
*/
|
|
54
|
+
update(position) {
|
|
55
|
+
if (!this.#destroyed) setPosition(this, position);
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
function createFloatable(anchor, content, options, standalone) {
|
|
59
|
+
if (!isHTMLOrSVGElement(anchor) || !isHTMLOrSVGElement(content)) throw new TypeError("Anchor and content must be an HTMLElement or SVGElement.");
|
|
60
|
+
let floatable = instances.get(content);
|
|
61
|
+
if (floatable == null) {
|
|
62
|
+
floatable = new Floatable(anchor, content, options, standalone);
|
|
63
|
+
instances.set(content, floatable);
|
|
64
|
+
}
|
|
65
|
+
return floatable;
|
|
66
|
+
}
|
|
67
|
+
function getAnchorName(anchor) {
|
|
68
|
+
let { anchorName } = anchor.style;
|
|
69
|
+
if (anchorName.length > 0) return anchorName;
|
|
70
|
+
index += 1;
|
|
71
|
+
return `--oui-floatable-anchor-${index}`;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Create a _Floatable_ for an anchor and content element
|
|
75
|
+
*
|
|
76
|
+
* @param anchor Anchor element
|
|
77
|
+
* @param content Content element
|
|
78
|
+
* @param options _Floatable_ options
|
|
79
|
+
* @returns _Floatable_ instance
|
|
80
|
+
*/
|
|
81
|
+
function getFloatable(anchor, content, options) {
|
|
82
|
+
const { open, position } = getOptions(options);
|
|
83
|
+
const floatable = createFloatable(anchor, content, { position }, true);
|
|
84
|
+
floatable?.update(position);
|
|
85
|
+
if (open) floatable?.show();
|
|
86
|
+
return floatable;
|
|
87
|
+
}
|
|
88
|
+
function getOptions(input) {
|
|
89
|
+
const object = isPlainObject(input) ? input : {};
|
|
90
|
+
return {
|
|
91
|
+
open: typeof object.open === "boolean" ? object.open : true,
|
|
92
|
+
position: object.position != null && object.position in AREAS ? object.position : "below"
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
function getPosition(floatable, override) {
|
|
96
|
+
if (override != null && override in AREAS) return override;
|
|
97
|
+
if (floatable.options.attribute == null) return floatable.options.position;
|
|
98
|
+
const attribute = floatable.anchor.getAttribute(floatable.options.attribute) ?? "";
|
|
99
|
+
return attribute in AREAS ? attribute : floatable.options.position;
|
|
100
|
+
}
|
|
101
|
+
function removeFloatable(content) {
|
|
102
|
+
instances.get(content)?.destroy();
|
|
103
|
+
instances.delete(content);
|
|
104
|
+
}
|
|
105
|
+
function setPosition(floatable, override) {
|
|
106
|
+
const area = AREAS[getPosition(floatable, override)];
|
|
107
|
+
floatable.content.style.positionArea = area;
|
|
108
|
+
floatable.content.setAttribute(ATTRIBUTE_POSITION, POSITIONS[area]);
|
|
109
|
+
}
|
|
110
|
+
const AREAS = {
|
|
111
|
+
above: "block-start",
|
|
112
|
+
"above-end": "start span-start",
|
|
113
|
+
"above-start": "start span-end",
|
|
114
|
+
below: "block-end",
|
|
115
|
+
"below-end": "end span-start",
|
|
116
|
+
"below-start": "end span-end",
|
|
117
|
+
end: "center end",
|
|
118
|
+
"end-bottom": "span-start end",
|
|
119
|
+
"end-top": "span-end end",
|
|
120
|
+
start: "center start",
|
|
121
|
+
"start-bottom": "span-start start",
|
|
122
|
+
"start-top": "span-end start"
|
|
123
|
+
};
|
|
124
|
+
const ATTRIBUTE_FLOATABLE = "oui-floatable";
|
|
125
|
+
const ATTRIBUTE_POSITION = "oui-position";
|
|
126
|
+
const POSITIONS = Object.fromEntries(Object.entries(AREAS).map(([position, area]) => [area, position]));
|
|
127
|
+
const instances = /* @__PURE__ */ new WeakMap();
|
|
128
|
+
let index = 0;
|
|
129
|
+
//#endregion
|
|
130
|
+
export { ATTRIBUTE_FLOATABLE, Floatable, createFloatable, getFloatable, removeFloatable };
|
package/dist/js/index.d.mts
CHANGED
package/dist/js/index.mjs
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
+
import { getFloatable } from "./floatable.mjs";
|
|
1
2
|
import "./focus-trap/standalone.mjs";
|
|
2
3
|
import "./movable.mjs";
|
|
3
4
|
import "./popover.mjs";
|
|
4
5
|
import "./sortable.mjs";
|
|
5
6
|
import { OuiSplitterElement } from "./splitter.mjs";
|
|
6
7
|
import { setTooltipDelay } from "./tooltip.mjs";
|
|
7
|
-
export { OuiSplitterElement, setTooltipDelay };
|
|
8
|
+
export { OuiSplitterElement, getFloatable, setTooltipDelay };
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
+
import { isHTMLOrSVGElement } from "@oscarpalmer/toretto/is";
|
|
1
2
|
//#region src/js/internal/attributable.ts
|
|
2
3
|
function attributable(attribute, add, remove) {
|
|
3
4
|
new MutationObserver((entries) => {
|
|
4
5
|
const { length } = entries;
|
|
5
6
|
for (let index = 0; index < length; index += 1) {
|
|
6
7
|
const entry = entries[index];
|
|
7
|
-
if (entry.type === "attributes" && entry.target
|
|
8
|
+
if (entry.type === "attributes" && isHTMLOrSVGElement(entry.target)) if (entry.target.hasAttribute(attribute)) handleAdded(attribute, [entry.target], add);
|
|
8
9
|
else handleRemoved([entry.target], remove);
|
|
9
10
|
else if (entry.type === "childList") {
|
|
10
11
|
handleAdded(attribute, entry.addedNodes, add);
|
|
@@ -22,7 +23,7 @@ function attributable(attribute, add, remove) {
|
|
|
22
23
|
});
|
|
23
24
|
}
|
|
24
25
|
function handleAdded(attribute, nodes, add) {
|
|
25
|
-
const elements = [...nodes].filter(
|
|
26
|
+
const elements = [...nodes].filter(isHTMLOrSVGElement);
|
|
26
27
|
const { length } = elements;
|
|
27
28
|
for (let index = 0; index < length; index += 1) {
|
|
28
29
|
const element = elements[index];
|
|
@@ -31,7 +32,7 @@ function handleAdded(attribute, nodes, add) {
|
|
|
31
32
|
}
|
|
32
33
|
}
|
|
33
34
|
function handleRemoved(nodes, remove) {
|
|
34
|
-
const elements = [...nodes].filter(
|
|
35
|
+
const elements = [...nodes].filter(isHTMLOrSVGElement);
|
|
35
36
|
const { length } = elements;
|
|
36
37
|
for (let index = 0; index < length; index += 1) {
|
|
37
38
|
const element = elements[index];
|
package/dist/js/popover.mjs
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
+
import { createFloatable, removeFloatable } from "./floatable.mjs";
|
|
1
2
|
import { attributable } from "./internal/attributable.mjs";
|
|
2
3
|
import { createFocusTrap, removeFocusTrap } from "./focus-trap/embedded.mjs";
|
|
3
|
-
import {
|
|
4
|
+
import { isHTMLOrSVGElement } from "@oscarpalmer/toretto/is";
|
|
4
5
|
import { on } from "@oscarpalmer/toretto/event";
|
|
5
6
|
//#region src/js/popover.ts
|
|
6
7
|
var Popover = class {
|
|
@@ -11,8 +12,11 @@ var Popover = class {
|
|
|
11
12
|
listener;
|
|
12
13
|
constructor(element) {
|
|
13
14
|
this.element = element;
|
|
14
|
-
|
|
15
|
-
|
|
15
|
+
const anchor = element.ownerDocument.querySelector(`[popovertarget="${element.id}"]`);
|
|
16
|
+
if (!isHTMLOrSVGElement(anchor)) throw new Error(`No anchor found for popover with id '${element.id}'.`);
|
|
17
|
+
if (element.contains(anchor)) throw new Error("Popover content cannot contain its anchor.");
|
|
18
|
+
this.anchor = anchor;
|
|
19
|
+
this.floatable = createFloatable(this.anchor, element, options, false);
|
|
16
20
|
this.focusTrap = createFocusTrap(element, { noescape: true });
|
|
17
21
|
this.listener = on(element, EVENT_TOGGLE, (event) => {
|
|
18
22
|
if (event.newState === STATE_OPEN) {
|
|
@@ -34,7 +38,7 @@ var Popover = class {
|
|
|
34
38
|
}
|
|
35
39
|
};
|
|
36
40
|
function onAdd(element) {
|
|
37
|
-
if (!instances.has(element) && element.getAttribute(ATTRIBUTE) !== POPOVER_HINT) instances.set(element, new Popover(element));
|
|
41
|
+
if (!instances.has(element) && element.getAttribute(ATTRIBUTE) !== POPOVER_HINT && !element.hasAttribute("oui-floatable")) instances.set(element, new Popover(element));
|
|
38
42
|
}
|
|
39
43
|
function onPointerdown() {
|
|
40
44
|
ignoreFocus = true;
|
|
@@ -50,7 +54,7 @@ const POPOVER_HINT = "hint";
|
|
|
50
54
|
const STATE_OPEN = "open";
|
|
51
55
|
const instances = /* @__PURE__ */ new WeakMap();
|
|
52
56
|
const options = {
|
|
53
|
-
attribute:
|
|
57
|
+
attribute: `${ATTRIBUTE}position`,
|
|
54
58
|
position: "below-start"
|
|
55
59
|
};
|
|
56
60
|
let ignoreFocus = false;
|
package/dist/js/sortable.mjs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { attributable } from "./internal/attributable.mjs";
|
|
2
2
|
import { Draggable, addDraggable, removeDraggable } from "./internal/draggable.mjs";
|
|
3
|
+
import { isHTMLOrSVGElement } from "@oscarpalmer/toretto/is";
|
|
3
4
|
import { getPosition } from "@oscarpalmer/toretto/event";
|
|
4
5
|
import { findAncestor, getElementFromPosition } from "@oscarpalmer/toretto/find";
|
|
5
6
|
import { createElement } from "@oscarpalmer/toretto/create";
|
|
@@ -177,12 +178,13 @@ function setPlaceholder(state, sortable, origin) {
|
|
|
177
178
|
parent: sortable.element,
|
|
178
179
|
placeholder: element,
|
|
179
180
|
create: (value) => {
|
|
180
|
-
if (!(value
|
|
181
|
-
|
|
182
|
-
|
|
181
|
+
if (!isHTMLOrSVGElement(value)) return;
|
|
182
|
+
const element = value;
|
|
183
|
+
placeholder.drag.custom = element;
|
|
184
|
+
updatePlaceholder(sortable, element);
|
|
183
185
|
state.element = {
|
|
184
|
-
rectangle:
|
|
185
|
-
node:
|
|
186
|
+
rectangle: element.getBoundingClientRect(),
|
|
187
|
+
node: element
|
|
186
188
|
};
|
|
187
189
|
}
|
|
188
190
|
}
|
package/dist/js/splitter.mjs
CHANGED
|
@@ -1,24 +1,28 @@
|
|
|
1
|
+
import { isNullableOrWhitespace } from "@oscarpalmer/atoms/is";
|
|
1
2
|
import { clamp } from "@oscarpalmer/atoms/number";
|
|
2
3
|
import { getPosition, on } from "@oscarpalmer/toretto/event";
|
|
3
4
|
import { findAncestor } from "@oscarpalmer/toretto/find";
|
|
4
5
|
import { toggleStyles } from "@oscarpalmer/toretto/style";
|
|
5
6
|
import supportsTouch from "@oscarpalmer/toretto/touch";
|
|
6
|
-
import { isNullableOrWhitespace } from "@oscarpalmer/atoms/is";
|
|
7
7
|
import { round } from "@oscarpalmer/atoms/math";
|
|
8
8
|
//#region src/js/splitter.ts
|
|
9
|
+
const ATTRIBUTE_MAX = "max";
|
|
10
|
+
const ATTRIBUTE_MIN = "min";
|
|
11
|
+
const ATTRIBUTE_SIZE = "size";
|
|
12
|
+
const ATTRIBUTE_TYPE = "type";
|
|
9
13
|
var OuiSplitterElement = class extends HTMLElement {
|
|
10
14
|
static observedAttributes = [
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
+
ATTRIBUTE_MAX,
|
|
16
|
+
ATTRIBUTE_MIN,
|
|
17
|
+
ATTRIBUTE_SIZE,
|
|
18
|
+
ATTRIBUTE_TYPE
|
|
15
19
|
];
|
|
16
20
|
#splitter;
|
|
17
21
|
get max() {
|
|
18
22
|
return this.#splitter.values.max;
|
|
19
23
|
}
|
|
20
24
|
set max(value) {
|
|
21
|
-
this.setAttribute(
|
|
25
|
+
this.setAttribute(ATTRIBUTE_MAX, String(value));
|
|
22
26
|
}
|
|
23
27
|
get min() {
|
|
24
28
|
return this.#splitter.values.min;
|
|
@@ -52,12 +56,12 @@ var OuiSplitterElement = class extends HTMLElement {
|
|
|
52
56
|
}
|
|
53
57
|
attributeChangedCallback(name) {
|
|
54
58
|
switch (name) {
|
|
55
|
-
case
|
|
56
|
-
case
|
|
57
|
-
case
|
|
59
|
+
case ATTRIBUTE_MAX:
|
|
60
|
+
case ATTRIBUTE_MIN:
|
|
61
|
+
case ATTRIBUTE_SIZE:
|
|
58
62
|
this.#splitter.updateValues();
|
|
59
63
|
break;
|
|
60
|
-
case
|
|
64
|
+
case ATTRIBUTE_TYPE:
|
|
61
65
|
this.#splitter.updateOrientation();
|
|
62
66
|
break;
|
|
63
67
|
default: break;
|
|
@@ -157,14 +161,14 @@ function getValue(element, name, defaultValue) {
|
|
|
157
161
|
}
|
|
158
162
|
function getValues(element) {
|
|
159
163
|
const values = {
|
|
160
|
-
max: getValue(element,
|
|
161
|
-
min: getValue(element,
|
|
164
|
+
max: getValue(element, ATTRIBUTE_MAX, NAVIGATION_MAXIMUM_PERCENTAGE),
|
|
165
|
+
min: getValue(element, ATTRIBUTE_MIN, NAVIGATION_MINIMUM_PERCENTAGE),
|
|
162
166
|
now: {
|
|
163
167
|
current: .5,
|
|
164
168
|
previous: .5
|
|
165
169
|
}
|
|
166
170
|
};
|
|
167
|
-
const value = getContained(values.min, getValue(element,
|
|
171
|
+
const value = getContained(values.min, getValue(element, ATTRIBUTE_SIZE, values.now.current), values.max);
|
|
168
172
|
values.now.current = value;
|
|
169
173
|
values.now.previous = value;
|
|
170
174
|
return values;
|
package/dist/js/tooltip.d.mts
CHANGED
|
@@ -1,4 +1,10 @@
|
|
|
1
1
|
//#region src/js/tooltip.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Set the delay before showing the tooltip, in milliseconds
|
|
4
|
+
*
|
|
5
|
+
* _(Default delay is half a second, or 500 milliseconds)_
|
|
6
|
+
* @param value Delay in milliseconds
|
|
7
|
+
*/
|
|
2
8
|
declare function setTooltipDelay(value: number): void;
|
|
3
9
|
//#endregion
|
|
4
10
|
export { setTooltipDelay };
|
package/dist/js/tooltip.mjs
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
+
import { Floatable } from "./floatable.mjs";
|
|
1
2
|
import { attributable } from "./internal/attributable.mjs";
|
|
2
|
-
import {
|
|
3
|
+
import { isNullableOrWhitespace } from "@oscarpalmer/atoms/is";
|
|
4
|
+
import { isHTMLOrSVGElement } from "@oscarpalmer/toretto/is";
|
|
3
5
|
import { on } from "@oscarpalmer/toretto/event";
|
|
4
6
|
import { findAncestor } from "@oscarpalmer/toretto/find";
|
|
5
7
|
import { createElement } from "@oscarpalmer/toretto/create";
|
|
6
|
-
import { isNullableOrWhitespace } from "@oscarpalmer/atoms/is";
|
|
7
8
|
//#region src/js/tooltip.ts
|
|
8
9
|
var Tooltip = class {
|
|
9
10
|
anchor;
|
|
@@ -15,7 +16,7 @@ var Tooltip = class {
|
|
|
15
16
|
this.content = content;
|
|
16
17
|
instances.set(content, this);
|
|
17
18
|
anchor.insertAdjacentElement("afterend", content);
|
|
18
|
-
this.floatable = new Floatable(anchor, content, options);
|
|
19
|
+
this.floatable = new Floatable(anchor, content, options, false);
|
|
19
20
|
this.floatable.update();
|
|
20
21
|
}
|
|
21
22
|
destroy() {
|
|
@@ -51,8 +52,8 @@ function activate(event, focus) {
|
|
|
51
52
|
function closeTooltip(instance) {
|
|
52
53
|
if (instance == null) return;
|
|
53
54
|
if (instance.timer != null) clearTimeout(instance.timer);
|
|
54
|
-
instance.content
|
|
55
|
-
instance.content
|
|
55
|
+
instance.content?.hidePopover();
|
|
56
|
+
instance.content?.removeAttribute(ATTRIBUTE_CONTENT_ACTIVE);
|
|
56
57
|
}
|
|
57
58
|
function createTooltip(anchor) {
|
|
58
59
|
const content = getContent(anchor);
|
|
@@ -64,7 +65,7 @@ function findElements(attribute) {
|
|
|
64
65
|
for (const id of ids) {
|
|
65
66
|
if (isNullableOrWhitespace(id)) continue;
|
|
66
67
|
const element = document.querySelector(`#${id}`);
|
|
67
|
-
if (element
|
|
68
|
+
if (isHTMLOrSVGElement(element)) {
|
|
68
69
|
const cloned = element.cloneNode(true);
|
|
69
70
|
cloned.hidden = false;
|
|
70
71
|
elements.push(cloned);
|
|
@@ -118,6 +119,12 @@ function onRemove(element) {
|
|
|
118
119
|
instances.get(element)?.destroy();
|
|
119
120
|
instances.delete(element);
|
|
120
121
|
}
|
|
122
|
+
/**
|
|
123
|
+
* Set the delay before showing the tooltip, in milliseconds
|
|
124
|
+
*
|
|
125
|
+
* _(Default delay is half a second, or 500 milliseconds)_
|
|
126
|
+
* @param value Delay in milliseconds
|
|
127
|
+
*/
|
|
121
128
|
function setTooltipDelay(value) {
|
|
122
129
|
if (typeof value === "number" && !Number.isNaN(value) && value >= 0) delay = value;
|
|
123
130
|
}
|
|
@@ -134,7 +141,7 @@ const SELECTOR = `[${ATTRIBUTE}]`;
|
|
|
134
141
|
const SELECTOR_CONTENT = `[${ATTRIBUTE_CONTENT}]`;
|
|
135
142
|
const instances = /* @__PURE__ */ new WeakMap();
|
|
136
143
|
const options = {
|
|
137
|
-
attribute:
|
|
144
|
+
attribute: `${ATTRIBUTE}-position`,
|
|
138
145
|
position: "above"
|
|
139
146
|
};
|
|
140
147
|
let delay = 500;
|
package/package.json
CHANGED
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
import {isPlainObject} from '@oscarpalmer/atoms/is';
|
|
2
|
+
import {isHTMLOrSVGElement} from '@oscarpalmer/toretto/is';
|
|
3
|
+
|
|
4
|
+
export class Floatable {
|
|
5
|
+
#destroyed = false;
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Is the _Floatable_ open?
|
|
9
|
+
*/
|
|
10
|
+
get open(): boolean {
|
|
11
|
+
return this.content.checkVisibility();
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
constructor(
|
|
15
|
+
public anchor: HTMLElement,
|
|
16
|
+
public content: HTMLElement,
|
|
17
|
+
public options: FloatableOptions,
|
|
18
|
+
standalone: boolean,
|
|
19
|
+
) {
|
|
20
|
+
if (standalone) {
|
|
21
|
+
content.setAttribute(ATTRIBUTE_FLOATABLE, '');
|
|
22
|
+
|
|
23
|
+
content.popover = 'manual';
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const anchorName = getAnchorName(anchor);
|
|
27
|
+
|
|
28
|
+
anchor.style.anchorName = anchorName;
|
|
29
|
+
content.style.positionAnchor = anchorName;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Destroy the floatable, removing it from the DOM and cleaning up references
|
|
34
|
+
*/
|
|
35
|
+
destroy(): void {
|
|
36
|
+
if (this.#destroyed) {
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
this.#destroyed = true;
|
|
41
|
+
|
|
42
|
+
removeFloatable(this.content);
|
|
43
|
+
|
|
44
|
+
this.anchor = null as never;
|
|
45
|
+
this.content = null as never;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Hide the floatable
|
|
50
|
+
*/
|
|
51
|
+
hide(): void {
|
|
52
|
+
if (!this.#destroyed) {
|
|
53
|
+
this.content.hidePopover();
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Show the floatable
|
|
59
|
+
*/
|
|
60
|
+
show(): void {
|
|
61
|
+
if (!this.#destroyed) {
|
|
62
|
+
this.content.showPopover();
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Update the position of the floatable, with an optional new position
|
|
68
|
+
*
|
|
69
|
+
* @param position Optional new position
|
|
70
|
+
*/
|
|
71
|
+
update(position?: FloatablePosition): void {
|
|
72
|
+
if (!this.#destroyed) {
|
|
73
|
+
setPosition(this, position);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export type FloatableOptions = {
|
|
79
|
+
attribute?: string;
|
|
80
|
+
position: FloatablePosition;
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
export type FloatablePosition =
|
|
84
|
+
| 'above'
|
|
85
|
+
| 'above-end'
|
|
86
|
+
| 'above-start'
|
|
87
|
+
| 'below'
|
|
88
|
+
| 'below-end'
|
|
89
|
+
| 'below-start'
|
|
90
|
+
| 'end'
|
|
91
|
+
| 'end-bottom'
|
|
92
|
+
| 'end-top'
|
|
93
|
+
| 'start'
|
|
94
|
+
| 'start-bottom'
|
|
95
|
+
| 'start-top';
|
|
96
|
+
|
|
97
|
+
type GetFloatableOptions = {
|
|
98
|
+
/**
|
|
99
|
+
* Open the _Floatable_ when created? _(defaults to `true`)_
|
|
100
|
+
*/
|
|
101
|
+
open?: boolean;
|
|
102
|
+
/**
|
|
103
|
+
* Position of the _Floatable_ _(defaults to `below`)_
|
|
104
|
+
*/
|
|
105
|
+
position?: FloatablePosition;
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
export function createFloatable(
|
|
109
|
+
anchor: HTMLElement,
|
|
110
|
+
content: HTMLElement,
|
|
111
|
+
options: FloatableOptions,
|
|
112
|
+
standalone: boolean,
|
|
113
|
+
): Floatable {
|
|
114
|
+
if (!isHTMLOrSVGElement(anchor) || !isHTMLOrSVGElement(content)) {
|
|
115
|
+
throw new TypeError('Anchor and content must be an HTMLElement or SVGElement.');
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
let floatable = instances.get(content);
|
|
119
|
+
|
|
120
|
+
if (floatable == null) {
|
|
121
|
+
floatable = new Floatable(anchor, content, options, standalone);
|
|
122
|
+
|
|
123
|
+
instances.set(content, floatable);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return floatable;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function getAnchorName(anchor: HTMLElement): string {
|
|
130
|
+
let {anchorName} = anchor.style;
|
|
131
|
+
|
|
132
|
+
if (anchorName.length > 0) {
|
|
133
|
+
return anchorName;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
index += 1;
|
|
137
|
+
|
|
138
|
+
return `--oui-floatable-anchor-${index}`;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Create a _Floatable_ for an anchor and content element
|
|
143
|
+
*
|
|
144
|
+
* @param anchor Anchor element
|
|
145
|
+
* @param content Content element
|
|
146
|
+
* @param options _Floatable_ options
|
|
147
|
+
* @returns _Floatable_ instance
|
|
148
|
+
*/
|
|
149
|
+
export function getFloatable(
|
|
150
|
+
anchor: HTMLElement | SVGElement,
|
|
151
|
+
content: HTMLElement | SVGElement,
|
|
152
|
+
options?: GetFloatableOptions,
|
|
153
|
+
): Floatable {
|
|
154
|
+
const {open, position} = getOptions(options);
|
|
155
|
+
|
|
156
|
+
const floatable = createFloatable(
|
|
157
|
+
anchor as HTMLElement,
|
|
158
|
+
content as HTMLElement,
|
|
159
|
+
{
|
|
160
|
+
position,
|
|
161
|
+
},
|
|
162
|
+
true,
|
|
163
|
+
);
|
|
164
|
+
|
|
165
|
+
floatable?.update(position);
|
|
166
|
+
|
|
167
|
+
if (open) {
|
|
168
|
+
floatable?.show();
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
return floatable;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function getOptions(input?: GetFloatableOptions): Required<GetFloatableOptions> {
|
|
175
|
+
const object = isPlainObject(input) ? input : {};
|
|
176
|
+
|
|
177
|
+
return {
|
|
178
|
+
open: typeof object.open === 'boolean' ? object.open : true,
|
|
179
|
+
position: object.position != null && object.position in AREAS ? object.position : 'below',
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function getPosition(floatable: Floatable, override?: FloatablePosition): FloatablePosition {
|
|
184
|
+
if (override != null && override in AREAS) {
|
|
185
|
+
return override;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
if (floatable.options.attribute == null) {
|
|
189
|
+
return floatable.options.position;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const attribute = floatable.anchor.getAttribute(floatable.options.attribute) ?? '';
|
|
193
|
+
|
|
194
|
+
return attribute in AREAS ? (attribute as FloatablePosition) : floatable.options.position;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
export function removeFloatable(content: HTMLElement): void {
|
|
198
|
+
instances.get(content)?.destroy();
|
|
199
|
+
instances.delete(content);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function setPosition(floatable: Floatable, override?: FloatablePosition): void {
|
|
203
|
+
const position = getPosition(floatable, override);
|
|
204
|
+
|
|
205
|
+
const area = AREAS[position];
|
|
206
|
+
|
|
207
|
+
floatable.content.style.positionArea = area;
|
|
208
|
+
|
|
209
|
+
floatable.content.setAttribute(ATTRIBUTE_POSITION, POSITIONS[area]);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
const AREAS: Record<FloatablePosition, string> = {
|
|
213
|
+
above: 'block-start',
|
|
214
|
+
'above-end': 'start span-start',
|
|
215
|
+
'above-start': 'start span-end',
|
|
216
|
+
below: 'block-end',
|
|
217
|
+
'below-end': 'end span-start',
|
|
218
|
+
'below-start': 'end span-end',
|
|
219
|
+
end: 'center end',
|
|
220
|
+
'end-bottom': 'span-start end',
|
|
221
|
+
'end-top': 'span-end end',
|
|
222
|
+
start: 'center start',
|
|
223
|
+
'start-bottom': 'span-start start',
|
|
224
|
+
'start-top': 'span-end start',
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
export const ATTRIBUTE_FLOATABLE = 'oui-floatable';
|
|
228
|
+
|
|
229
|
+
const ATTRIBUTE_POSITION = 'oui-position';
|
|
230
|
+
|
|
231
|
+
const POSITIONS = Object.fromEntries(
|
|
232
|
+
Object.entries(AREAS).map(([position, area]) => [area, position]),
|
|
233
|
+
) as Record<string, FloatablePosition>;
|
|
234
|
+
|
|
235
|
+
const instances = new WeakMap<HTMLElement, Floatable>();
|
|
236
|
+
|
|
237
|
+
let index = 0;
|
package/src/js/index.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import {isHTMLOrSVGElement} from '@oscarpalmer/toretto/is';
|
|
2
|
+
|
|
1
3
|
export function attributable(
|
|
2
4
|
attribute: string,
|
|
3
5
|
add: (element: HTMLElement) => void,
|
|
@@ -9,7 +11,7 @@ export function attributable(
|
|
|
9
11
|
for (let index = 0; index < length; index += 1) {
|
|
10
12
|
const entry = entries[index];
|
|
11
13
|
|
|
12
|
-
if (entry.type === 'attributes' && entry.target
|
|
14
|
+
if (entry.type === 'attributes' && isHTMLOrSVGElement(entry.target)) {
|
|
13
15
|
if (entry.target.hasAttribute(attribute)) {
|
|
14
16
|
handleAdded(attribute, [entry.target], add);
|
|
15
17
|
} else {
|
|
@@ -39,7 +41,7 @@ function handleAdded(
|
|
|
39
41
|
nodes: Node[] | NodeList,
|
|
40
42
|
add: (element: HTMLElement) => void,
|
|
41
43
|
): void {
|
|
42
|
-
const elements = [...nodes].filter(
|
|
44
|
+
const elements = [...nodes].filter(isHTMLOrSVGElement) as HTMLElement[];
|
|
43
45
|
|
|
44
46
|
const {length} = elements;
|
|
45
47
|
|
|
@@ -57,7 +59,7 @@ function handleAdded(
|
|
|
57
59
|
}
|
|
58
60
|
|
|
59
61
|
function handleRemoved(nodes: Node[] | NodeList, remove: (element: HTMLElement) => void): void {
|
|
60
|
-
const elements = [...nodes].filter(
|
|
62
|
+
const elements = [...nodes].filter(isHTMLOrSVGElement) as HTMLElement[];
|
|
61
63
|
|
|
62
64
|
const {length} = elements;
|
|
63
65
|
|
package/src/js/popover.ts
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
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
7
|
removeFloatable,
|
|
8
8
|
type Floatable,
|
|
9
9
|
type FloatableOptions,
|
|
10
|
-
} from './
|
|
10
|
+
} from './floatable';
|
|
11
|
+
import {createFocusTrap, removeFocusTrap, type FocusTrap} from './focus-trap/embedded';
|
|
12
|
+
import {attributable} from './internal/attributable';
|
|
11
13
|
|
|
12
14
|
class Popover {
|
|
13
15
|
anchor: HTMLElement | null;
|
|
@@ -16,9 +18,19 @@ class Popover {
|
|
|
16
18
|
listener: RemovableEventListener;
|
|
17
19
|
|
|
18
20
|
constructor(public element: HTMLElement) {
|
|
19
|
-
|
|
21
|
+
const anchor = element.ownerDocument.querySelector(`[popovertarget="${element.id}"]`);
|
|
22
|
+
|
|
23
|
+
if (!isHTMLOrSVGElement(anchor)) {
|
|
24
|
+
throw new Error(`No anchor found for popover with id '${element.id}'.`);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (element.contains(anchor)) {
|
|
28
|
+
throw new Error('Popover content cannot contain its anchor.');
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
this.anchor = anchor as HTMLElement;
|
|
20
32
|
|
|
21
|
-
this.floatable = createFloatable(this.anchor
|
|
33
|
+
this.floatable = createFloatable(this.anchor, element, options, false);
|
|
22
34
|
|
|
23
35
|
this.focusTrap = createFocusTrap(element, {
|
|
24
36
|
noescape: true,
|
|
@@ -55,7 +67,11 @@ class Popover {
|
|
|
55
67
|
}
|
|
56
68
|
|
|
57
69
|
function onAdd(element: HTMLElement): void {
|
|
58
|
-
if (
|
|
70
|
+
if (
|
|
71
|
+
!instances.has(element) &&
|
|
72
|
+
element.getAttribute(ATTRIBUTE) !== POPOVER_HINT &&
|
|
73
|
+
!element.hasAttribute(ATTRIBUTE_FLOATABLE)
|
|
74
|
+
) {
|
|
59
75
|
instances.set(element, new Popover(element));
|
|
60
76
|
}
|
|
61
77
|
}
|
|
@@ -83,7 +99,7 @@ const STATE_OPEN = 'open';
|
|
|
83
99
|
const instances = new WeakMap<HTMLElement, Popover>();
|
|
84
100
|
|
|
85
101
|
const options: FloatableOptions = {
|
|
86
|
-
attribute:
|
|
102
|
+
attribute: `${ATTRIBUTE}position`,
|
|
87
103
|
position: 'below-start',
|
|
88
104
|
};
|
|
89
105
|
|
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,8 +2,9 @@ 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';
|
|
5
6
|
import {attributable} from './internal/attributable';
|
|
6
|
-
import {Floatable, type FloatableOptions} from './
|
|
7
|
+
import {Floatable, type FloatableOptions} from './floatable';
|
|
7
8
|
|
|
8
9
|
class Tooltip {
|
|
9
10
|
floatable: Floatable;
|
|
@@ -18,7 +19,7 @@ class Tooltip {
|
|
|
18
19
|
|
|
19
20
|
anchor.insertAdjacentElement('afterend', content);
|
|
20
21
|
|
|
21
|
-
this.floatable = new Floatable(anchor, content, options);
|
|
22
|
+
this.floatable = new Floatable(anchor, content, options, false);
|
|
22
23
|
|
|
23
24
|
this.floatable.update();
|
|
24
25
|
}
|
|
@@ -61,7 +62,7 @@ function activate(event: Event, focus: boolean): void {
|
|
|
61
62
|
instance.timer = setTimeout(
|
|
62
63
|
() => {
|
|
63
64
|
instance.content.showPopover({
|
|
64
|
-
source: instance.anchor,
|
|
65
|
+
source: instance.anchor as HTMLElement,
|
|
65
66
|
});
|
|
66
67
|
|
|
67
68
|
setTimeout(() => {
|
|
@@ -83,8 +84,8 @@ function closeTooltip(instance?: Tooltip): void {
|
|
|
83
84
|
clearTimeout(instance.timer);
|
|
84
85
|
}
|
|
85
86
|
|
|
86
|
-
instance.content
|
|
87
|
-
instance.content
|
|
87
|
+
instance.content?.hidePopover();
|
|
88
|
+
instance.content?.removeAttribute(ATTRIBUTE_CONTENT_ACTIVE);
|
|
88
89
|
}
|
|
89
90
|
|
|
90
91
|
function createTooltip(anchor: HTMLElement): void {
|
|
@@ -106,7 +107,7 @@ function findElements(attribute: string | null): HTMLElement[] {
|
|
|
106
107
|
|
|
107
108
|
const element = document.querySelector(`#${id}`);
|
|
108
109
|
|
|
109
|
-
if (element
|
|
110
|
+
if (isHTMLOrSVGElement(element)) {
|
|
110
111
|
const cloned = element.cloneNode(true) as HTMLElement;
|
|
111
112
|
|
|
112
113
|
cloned.hidden = false;
|
|
@@ -212,6 +213,12 @@ function onRemove(element: HTMLElement): void {
|
|
|
212
213
|
instances.delete(element);
|
|
213
214
|
}
|
|
214
215
|
|
|
216
|
+
/**
|
|
217
|
+
* Set the delay before showing the tooltip, in milliseconds
|
|
218
|
+
*
|
|
219
|
+
* _(Default delay is half a second, or 500 milliseconds)_
|
|
220
|
+
* @param value Delay in milliseconds
|
|
221
|
+
*/
|
|
215
222
|
export function setTooltipDelay(value: number): void {
|
|
216
223
|
if (typeof value === 'number' && !Number.isNaN(value) && value >= 0) {
|
|
217
224
|
delay = value;
|
|
@@ -243,7 +250,7 @@ const SELECTOR_CONTENT = `[${ATTRIBUTE_CONTENT}]`;
|
|
|
243
250
|
const instances = new WeakMap<HTMLElement, Tooltip>();
|
|
244
251
|
|
|
245
252
|
const options: FloatableOptions = {
|
|
246
|
-
attribute:
|
|
253
|
+
attribute: `${ATTRIBUTE}-position`,
|
|
247
254
|
position: 'above',
|
|
248
255
|
};
|
|
249
256
|
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
//#region src/js/internal/floatable.d.ts
|
|
2
|
-
declare class Floatable {
|
|
3
|
-
anchor: HTMLElement;
|
|
4
|
-
content: HTMLElement;
|
|
5
|
-
options: FloatableOptions;
|
|
6
|
-
constructor(anchor: HTMLElement, content: HTMLElement, options: FloatableOptions);
|
|
7
|
-
destroy(): void;
|
|
8
|
-
update(): void;
|
|
9
|
-
}
|
|
10
|
-
type FloatableOptions = {
|
|
11
|
-
attribute: string;
|
|
12
|
-
position: FloatablePosition;
|
|
13
|
-
};
|
|
14
|
-
type FloatablePosition = 'above' | 'above-end' | 'above-start' | 'below' | 'below-end' | 'below-start' | 'end' | 'end-bottom' | 'end-top' | 'start' | 'start-bottom' | 'start-top';
|
|
15
|
-
declare function createFloatable(anchor: HTMLElement | null, content: HTMLElement, options: FloatableOptions): Floatable | undefined;
|
|
16
|
-
declare function removeFloatable(content: HTMLElement): void;
|
|
17
|
-
//#endregion
|
|
18
|
-
export { Floatable, FloatableOptions, FloatablePosition, createFloatable, removeFloatable };
|
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
//#region src/js/internal/floatable.ts
|
|
2
|
-
var Floatable = class {
|
|
3
|
-
anchor;
|
|
4
|
-
content;
|
|
5
|
-
options;
|
|
6
|
-
constructor(anchor, content, options) {
|
|
7
|
-
this.anchor = anchor;
|
|
8
|
-
this.content = content;
|
|
9
|
-
this.options = options;
|
|
10
|
-
const anchorName = getAnchorName(anchor);
|
|
11
|
-
anchor.style.anchorName = anchorName;
|
|
12
|
-
content.style.positionAnchor = anchorName;
|
|
13
|
-
}
|
|
14
|
-
destroy() {
|
|
15
|
-
this.anchor = null;
|
|
16
|
-
this.content = null;
|
|
17
|
-
}
|
|
18
|
-
update() {
|
|
19
|
-
setPosition(this);
|
|
20
|
-
}
|
|
21
|
-
};
|
|
22
|
-
function createFloatable(anchor, content, options) {
|
|
23
|
-
if (anchor == null) return;
|
|
24
|
-
let floatable = instances.get(content);
|
|
25
|
-
if (floatable == null) {
|
|
26
|
-
floatable = new Floatable(anchor, content, options);
|
|
27
|
-
instances.set(content, floatable);
|
|
28
|
-
}
|
|
29
|
-
return floatable;
|
|
30
|
-
}
|
|
31
|
-
function getAnchorName(anchor) {
|
|
32
|
-
let { anchorName } = anchor.style;
|
|
33
|
-
if (anchorName.length > 0) return anchorName;
|
|
34
|
-
index += 1;
|
|
35
|
-
return `--oui-floatable-anchor-${index}`;
|
|
36
|
-
}
|
|
37
|
-
function removeFloatable(content) {
|
|
38
|
-
instances.get(content)?.destroy();
|
|
39
|
-
instances.delete(content);
|
|
40
|
-
}
|
|
41
|
-
function setPosition(floatable) {
|
|
42
|
-
let position = floatable.anchor.getAttribute(floatable.options.attribute) ?? floatable.options.position;
|
|
43
|
-
if (AREAS[position] == null) position = floatable.options.position;
|
|
44
|
-
const area = AREAS[position];
|
|
45
|
-
floatable.content.style.positionArea = area;
|
|
46
|
-
floatable.content.setAttribute(ATTRIBUTE, POSITIONS[area]);
|
|
47
|
-
}
|
|
48
|
-
const AREAS = {
|
|
49
|
-
above: "block-start",
|
|
50
|
-
"above-end": "start span-start",
|
|
51
|
-
"above-start": "start span-end",
|
|
52
|
-
below: "block-end",
|
|
53
|
-
"below-end": "end span-start",
|
|
54
|
-
"below-start": "end span-end",
|
|
55
|
-
end: "center end",
|
|
56
|
-
"end-bottom": "span-start end",
|
|
57
|
-
"end-top": "span-end end",
|
|
58
|
-
start: "center start",
|
|
59
|
-
"start-bottom": "span-start start",
|
|
60
|
-
"start-top": "span-end start"
|
|
61
|
-
};
|
|
62
|
-
const ATTRIBUTE = "oui-position";
|
|
63
|
-
const POSITIONS = Object.fromEntries(Object.entries(AREAS).map(([position, area]) => [area, position]));
|
|
64
|
-
const instances = /* @__PURE__ */ new WeakMap();
|
|
65
|
-
let index = 0;
|
|
66
|
-
//#endregion
|
|
67
|
-
export { Floatable, createFloatable, removeFloatable };
|
|
@@ -1,118 +0,0 @@
|
|
|
1
|
-
export class Floatable {
|
|
2
|
-
constructor(
|
|
3
|
-
public anchor: HTMLElement,
|
|
4
|
-
public content: HTMLElement,
|
|
5
|
-
public options: FloatableOptions,
|
|
6
|
-
) {
|
|
7
|
-
const anchorName = getAnchorName(anchor);
|
|
8
|
-
|
|
9
|
-
anchor.style.anchorName = anchorName;
|
|
10
|
-
content.style.positionAnchor = anchorName;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
destroy(): void {
|
|
14
|
-
this.anchor = null as never;
|
|
15
|
-
this.content = null as never;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
update(): void {
|
|
19
|
-
setPosition(this);
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export type FloatableOptions = {
|
|
24
|
-
attribute: string;
|
|
25
|
-
position: FloatablePosition;
|
|
26
|
-
};
|
|
27
|
-
|
|
28
|
-
export type FloatablePosition =
|
|
29
|
-
| 'above'
|
|
30
|
-
| 'above-end'
|
|
31
|
-
| 'above-start'
|
|
32
|
-
| 'below'
|
|
33
|
-
| 'below-end'
|
|
34
|
-
| 'below-start'
|
|
35
|
-
| 'end'
|
|
36
|
-
| 'end-bottom'
|
|
37
|
-
| 'end-top'
|
|
38
|
-
| 'start'
|
|
39
|
-
| 'start-bottom'
|
|
40
|
-
| 'start-top';
|
|
41
|
-
|
|
42
|
-
export function createFloatable(
|
|
43
|
-
anchor: HTMLElement | null,
|
|
44
|
-
content: HTMLElement,
|
|
45
|
-
options: FloatableOptions,
|
|
46
|
-
): Floatable | undefined {
|
|
47
|
-
if (anchor == null) {
|
|
48
|
-
return;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
let floatable = instances.get(content);
|
|
52
|
-
|
|
53
|
-
if (floatable == null) {
|
|
54
|
-
floatable = new Floatable(anchor, content, options);
|
|
55
|
-
|
|
56
|
-
instances.set(content, floatable);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
return floatable;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
function getAnchorName(anchor: HTMLElement): string {
|
|
63
|
-
let {anchorName} = anchor.style;
|
|
64
|
-
|
|
65
|
-
if (anchorName.length > 0) {
|
|
66
|
-
return anchorName;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
index += 1;
|
|
70
|
-
|
|
71
|
-
return `--oui-floatable-anchor-${index}`;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
export function removeFloatable(content: HTMLElement): void {
|
|
75
|
-
instances.get(content)?.destroy();
|
|
76
|
-
instances.delete(content);
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
function setPosition(floatable: Floatable): void {
|
|
80
|
-
let position: FloatablePosition =
|
|
81
|
-
(floatable.anchor.getAttribute(floatable.options.attribute) as FloatablePosition) ??
|
|
82
|
-
floatable.options.position;
|
|
83
|
-
|
|
84
|
-
if (AREAS[position] == null) {
|
|
85
|
-
position = floatable.options.position;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
const area = AREAS[position];
|
|
89
|
-
|
|
90
|
-
floatable.content.style.positionArea = area;
|
|
91
|
-
|
|
92
|
-
floatable.content.setAttribute(ATTRIBUTE, POSITIONS[area]);
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
const AREAS: Record<FloatablePosition, string> = {
|
|
96
|
-
above: 'block-start',
|
|
97
|
-
'above-end': 'start span-start',
|
|
98
|
-
'above-start': 'start span-end',
|
|
99
|
-
below: 'block-end',
|
|
100
|
-
'below-end': 'end span-start',
|
|
101
|
-
'below-start': 'end span-end',
|
|
102
|
-
end: 'center end',
|
|
103
|
-
'end-bottom': 'span-start end',
|
|
104
|
-
'end-top': 'span-end end',
|
|
105
|
-
start: 'center start',
|
|
106
|
-
'start-bottom': 'span-start start',
|
|
107
|
-
'start-top': 'span-end start',
|
|
108
|
-
};
|
|
109
|
-
|
|
110
|
-
const ATTRIBUTE = 'oui-position';
|
|
111
|
-
|
|
112
|
-
const POSITIONS = Object.fromEntries(
|
|
113
|
-
Object.entries(AREAS).map(([position, area]) => [area, position]),
|
|
114
|
-
) as Record<string, FloatablePosition>;
|
|
115
|
-
|
|
116
|
-
const instances = new WeakMap<HTMLElement, Floatable>();
|
|
117
|
-
|
|
118
|
-
let index = 0;
|