@oscarpalmer/oui 0.27.0 → 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 -0
- package/dist/js/dialog.d.mts +2 -0
- package/dist/js/dialog.mjs +17 -0
- package/dist/js/index.d.mts +3 -2
- package/dist/js/index.mjs +5 -2
- package/dist/js/internal/draggable.mjs +1 -1
- 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/splitter.mjs +1 -1
- package/package.json +5 -1
- package/src/css/floatable.scss +9 -0
- package/src/css/form.scss +6 -6
- package/src/js/dialog.ts +30 -0
- package/src/js/index.ts +2 -1
- package/src/js/internal/draggable.ts +1 -1
- package/src/js/resizable/resizable.embedded.ts +383 -0
- package/src/js/resizable/resizable.standalone.ts +5 -0
- package/src/js/splitter.ts +1 -1
package/dist/css/floatable.css
CHANGED
|
@@ -30,6 +30,15 @@
|
|
|
30
30
|
transition-delay: 0.125s;
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
+
:where([oui-dialog]) {
|
|
34
|
+
position: relative;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
:where([oui-dialog-resizer]) {
|
|
38
|
+
position: absolute;
|
|
39
|
+
inset: auto 0 0 auto;
|
|
40
|
+
}
|
|
41
|
+
|
|
33
42
|
:where([oui-popover], [oui-tooltip-content]) {
|
|
34
43
|
--margin-bottom: 0.5em;
|
|
35
44
|
--margin-left: 0.5em;
|
package/dist/js/dialog.d.mts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { h as OuiMovable } from "./draggable-zszaW1qS.mjs";
|
|
2
|
+
import { n as OuiResizable } from "./resizable.embedded-CXrUVPzR.mjs";
|
|
2
3
|
import { RemovableEventListener } from "@oscarpalmer/toretto/models";
|
|
3
4
|
|
|
4
5
|
//#region src/js/dialog.d.ts
|
|
@@ -32,6 +33,7 @@ type OuiDialogState = {
|
|
|
32
33
|
listeners: RemovableEventListener[];
|
|
33
34
|
modal: boolean;
|
|
34
35
|
movable?: OuiMovable;
|
|
36
|
+
resizable?: OuiResizable;
|
|
35
37
|
};
|
|
36
38
|
/**
|
|
37
39
|
* Get the _OuiDialog_ for an element
|
package/dist/js/dialog.mjs
CHANGED
|
@@ -2,6 +2,7 @@ import { getOnBeforeToggleListener } from "./floatable/floatable.embedded.mjs";
|
|
|
2
2
|
import { attributable } from "./internal/attributable.mjs";
|
|
3
3
|
import { ATTRIBUTE_MOVABLE_HANDLE, createMovable } from "./movable/movable.embedded.mjs";
|
|
4
4
|
import "./movable/movable.standalone.mjs";
|
|
5
|
+
import { RESIZABLE_ATTRIBUTE_HANDLE, createResizable } from "./resizable/resizable.embedded.mjs";
|
|
5
6
|
import { on } from "@oscarpalmer/toretto/event";
|
|
6
7
|
//#region src/js/dialog.ts
|
|
7
8
|
var OuiDialog = class {
|
|
@@ -48,9 +49,13 @@ function destroyDialog(state) {
|
|
|
48
49
|
state.element.setAttribute(ARIA_MODAL, String(state.modal));
|
|
49
50
|
state.element.removeAttribute(ATTRIBUTE_OPEN);
|
|
50
51
|
for (const listener of state.listeners) listener();
|
|
52
|
+
state.movable?.destroy();
|
|
53
|
+
state.resizable?.destroy();
|
|
51
54
|
state.dialog = void 0;
|
|
52
55
|
state.element = void 0;
|
|
53
56
|
state.listeners = void 0;
|
|
57
|
+
state.movable = void 0;
|
|
58
|
+
state.resizable = void 0;
|
|
54
59
|
}
|
|
55
60
|
/**
|
|
56
61
|
* Get the _OuiDialog_ for an element
|
|
@@ -67,6 +72,12 @@ function getMovable(element) {
|
|
|
67
72
|
for (const handle of handles) handle.setAttribute(ATTRIBUTE_MOVABLE_HANDLE, "");
|
|
68
73
|
return createMovable(element, { container: element.getAttribute(ATTRIBUTE_CONTAINER) ?? void 0 });
|
|
69
74
|
}
|
|
75
|
+
function getResizable(element) {
|
|
76
|
+
const handle = element.querySelector(SELECTOR_RESIZER);
|
|
77
|
+
if (handle == null) return;
|
|
78
|
+
handle.setAttribute(RESIZABLE_ATTRIBUTE_HANDLE, "");
|
|
79
|
+
return createResizable(element);
|
|
80
|
+
}
|
|
70
81
|
function getState(element) {
|
|
71
82
|
const state = {
|
|
72
83
|
element,
|
|
@@ -78,18 +89,23 @@ function getState(element) {
|
|
|
78
89
|
element.setAttribute(ARIA_MODAL, String(modal));
|
|
79
90
|
element.setAttribute(ATTRIBUTE_OPEN, "");
|
|
80
91
|
element.focus();
|
|
92
|
+
setTimeout(() => {
|
|
93
|
+
state.resizable?.set();
|
|
94
|
+
}, 500);
|
|
81
95
|
} else {
|
|
82
96
|
element.removeAttribute(ATTRIBUTE_OPEN);
|
|
83
97
|
element.setAttribute(ARIA_MODAL, String(state.modal));
|
|
84
98
|
element.style.inset = "";
|
|
85
99
|
element.style.position = "";
|
|
86
100
|
element.style.transform = "";
|
|
101
|
+
state.resizable?.reset();
|
|
87
102
|
}
|
|
88
103
|
})],
|
|
89
104
|
modal: element.getAttribute(ARIA_MODAL) === TRUE
|
|
90
105
|
};
|
|
91
106
|
state.dialog = new OuiDialog(state);
|
|
92
107
|
state.movable = getMovable(element);
|
|
108
|
+
state.resizable = getResizable(element);
|
|
93
109
|
return state;
|
|
94
110
|
}
|
|
95
111
|
function removeDialog(element) {
|
|
@@ -107,6 +123,7 @@ const EVENT_TOGGLE = "toggle";
|
|
|
107
123
|
const MESSAGE = "The element must be an instance of HTMLDialogElement";
|
|
108
124
|
const SELECTOR_MOVER = `[${ATTRIBUTE}-mover]`;
|
|
109
125
|
const SELECTOR_MODAL = ":modal";
|
|
126
|
+
const SELECTOR_RESIZER = `[${ATTRIBUTE}-resizer]`;
|
|
110
127
|
const TRUE = "true";
|
|
111
128
|
const states = /* @__PURE__ */ new WeakMap();
|
|
112
129
|
attributable(ATTRIBUTE, addDialog, removeDialog);
|
package/dist/js/index.d.mts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import { g as createMovable, h as OuiMovable, t as CreateOuiDraggableOptions } from "./draggable-zszaW1qS.mjs";
|
|
1
|
+
import { d as OuiSortable, f as createSortable, g as createMovable, h as OuiMovable, t as CreateOuiDraggableOptions } from "./draggable-zszaW1qS.mjs";
|
|
2
|
+
import { a as createResizable, n as OuiResizable, t as CreateOuiResizableOptions } from "./resizable.embedded-CXrUVPzR.mjs";
|
|
2
3
|
import { OuiDialog, getDialog } from "./dialog.mjs";
|
|
3
4
|
import { c as createFloatable, i as OuiFloatable, n as CreateOuiFloatableOptions, o as OuiFloatablePosition } from "./floatable.embedded-YxGwzgbE.mjs";
|
|
4
5
|
import { a as OuiFocusTrapOptions, i as OuiFocusTrap, o as createFocusTrap } from "./focus-trap.embedded-DR8NpZfr.mjs";
|
|
5
6
|
import { OuiPopover, getPopover } from "./popover.mjs";
|
|
6
7
|
import { OuiSplitterElement } from "./splitter.mjs";
|
|
7
8
|
import { OuiTooltip, getTooltip, setTooltipDelay } from "./tooltip.mjs";
|
|
8
|
-
export { type CreateOuiFloatableOptions, type CreateOuiDraggableOptions as CreateOuiMovableOptions, type OuiDialog, type OuiFloatable, type OuiFloatablePosition, type OuiFocusTrap, type OuiFocusTrapOptions, type OuiMovable, type OuiPopover, OuiSplitterElement, type OuiTooltip, createFloatable, createFocusTrap, createMovable, getDialog, getPopover, getTooltip, setTooltipDelay };
|
|
9
|
+
export { type CreateOuiFloatableOptions, type CreateOuiDraggableOptions as CreateOuiMovableOptions, type CreateOuiDraggableOptions as CreateOuiSortableOptions, type CreateOuiResizableOptions, type OuiDialog, type OuiFloatable, type OuiFloatablePosition, type OuiFocusTrap, type OuiFocusTrapOptions, type OuiMovable, type OuiPopover, type OuiResizable, type OuiSortable, OuiSplitterElement, type OuiTooltip, createFloatable, createFocusTrap, createMovable, createResizable as createOuiResizable, createSortable, getDialog, getPopover, getTooltip, setTooltipDelay };
|
package/dist/js/index.mjs
CHANGED
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
import { createFloatable } from "./floatable/floatable.embedded.mjs";
|
|
2
2
|
import { createMovable } from "./movable/movable.embedded.mjs";
|
|
3
3
|
import "./movable/movable.standalone.mjs";
|
|
4
|
+
import { createResizable } from "./resizable/resizable.embedded.mjs";
|
|
4
5
|
import { getDialog } from "./dialog.mjs";
|
|
5
|
-
import "./sortable/sortable.embedded.mjs";
|
|
6
6
|
import "./toolbar.mjs";
|
|
7
7
|
import "./floatable/floatable.standalone.mjs";
|
|
8
8
|
import { createFocusTrap } from "./focus-trap/focus-trap.embedded.mjs";
|
|
9
9
|
import "./focus-trap/focus-trap.standalone.mjs";
|
|
10
10
|
import { getPopover } from "./popover.mjs";
|
|
11
|
+
import "./resizable/resizable.standalone.mjs";
|
|
12
|
+
import { createSortable } from "./sortable/sortable.embedded.mjs";
|
|
13
|
+
import "./sortable/sortable.standalone.mjs";
|
|
11
14
|
import { OuiSplitterElement } from "./splitter.mjs";
|
|
12
15
|
import { getTooltip, setTooltipDelay } from "./tooltip.mjs";
|
|
13
|
-
export { OuiSplitterElement, createFloatable, createFocusTrap, createMovable, getDialog, getPopover, getTooltip, setTooltipDelay };
|
|
16
|
+
export { OuiSplitterElement, createFloatable, createFocusTrap, createMovable, createResizable as createOuiResizable, createSortable, getDialog, getPopover, getTooltip, setTooltipDelay };
|
|
@@ -261,7 +261,7 @@ on(document, "keydown", onKeydown);
|
|
|
261
261
|
on(document, "mousedown", onPointerdown);
|
|
262
262
|
on(document, "pointermove", onPointermove);
|
|
263
263
|
on(document, "pointerup", onPointerup);
|
|
264
|
-
on(document, "touchcancel", onPointerup
|
|
264
|
+
on(document, "touchcancel", onPointerup);
|
|
265
265
|
on(document, "touchstart", onPointerdown, { passive: false });
|
|
266
266
|
//#endregion
|
|
267
267
|
export { OuiDraggable, addDraggable, getDraggableStates, removeDraggable };
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import { a as createResizable, i as RESIZABLE_ATTRIBUTE_HANDLE, n as OuiResizable, r as RESIZABLE_ATTRIBUTE, t as CreateOuiResizableOptions } from "../resizable.embedded-CXrUVPzR.mjs";
|
|
2
|
+
export { CreateOuiResizableOptions, OuiResizable, RESIZABLE_ATTRIBUTE, RESIZABLE_ATTRIBUTE_HANDLE, createResizable };
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import { attributable } from "../internal/attributable.mjs";
|
|
2
|
+
import { getPosition, on } from "@oscarpalmer/toretto/event";
|
|
3
|
+
import { isPlainObject } from "@oscarpalmer/atoms/is";
|
|
4
|
+
import { isHTMLOrSVGElement } from "@oscarpalmer/toretto/is";
|
|
5
|
+
import { getNumber } from "@oscarpalmer/atoms/number";
|
|
6
|
+
import { findAncestor } from "@oscarpalmer/toretto/find";
|
|
7
|
+
import { toggleStyles } from "@oscarpalmer/toretto/style";
|
|
8
|
+
import supportsTouch from "@oscarpalmer/toretto/touch";
|
|
9
|
+
//#region src/js/resizable/resizable.embedded.ts
|
|
10
|
+
var OuiResizable = class {
|
|
11
|
+
#state;
|
|
12
|
+
constructor(state) {
|
|
13
|
+
this.#state = state;
|
|
14
|
+
}
|
|
15
|
+
destroy() {
|
|
16
|
+
destroyResizable(this.#state);
|
|
17
|
+
}
|
|
18
|
+
set() {
|
|
19
|
+
const [width, height] = getAndSetDimensions(this.#state.element, this.#state.options);
|
|
20
|
+
console.log("set", width, height);
|
|
21
|
+
this.#state.width = width;
|
|
22
|
+
this.#state.height = height;
|
|
23
|
+
}
|
|
24
|
+
reset() {
|
|
25
|
+
this.#state.element.style.height = "";
|
|
26
|
+
this.#state.element.style.width = "";
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
function addResizable(element, options) {
|
|
30
|
+
let item = items.get(element);
|
|
31
|
+
if (item == null) {
|
|
32
|
+
const handle = element.querySelector(SELECTOR_HANDLE_SCOPED);
|
|
33
|
+
if (!isHTMLOrSVGElement(handle)) return;
|
|
34
|
+
const state = getState(element, handle, options);
|
|
35
|
+
item = {
|
|
36
|
+
instance: new OuiResizable(state),
|
|
37
|
+
state
|
|
38
|
+
};
|
|
39
|
+
items.set(element, item);
|
|
40
|
+
}
|
|
41
|
+
return item?.instance;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Creates _(or retrieves)_ a _OuiResizable_ instance for an element
|
|
45
|
+
*
|
|
46
|
+
* @param element Element to make resizable
|
|
47
|
+
* @returns _OuiResizable_ instance
|
|
48
|
+
*/
|
|
49
|
+
function createResizable(element, options) {
|
|
50
|
+
if (!isHTMLOrSVGElement(element)) throw new TypeError(MESSAGE_ELEMENT);
|
|
51
|
+
if (!isHTMLOrSVGElement(element.querySelector(SELECTOR_HANDLE_SCOPED))) throw new Error(MESSAGE_HANDLE);
|
|
52
|
+
return addResizable(element, options ?? {});
|
|
53
|
+
}
|
|
54
|
+
function destroyResizable(state) {
|
|
55
|
+
state.element = void 0;
|
|
56
|
+
state.handle = void 0;
|
|
57
|
+
}
|
|
58
|
+
function getAndSetDimensions(element, options, input) {
|
|
59
|
+
let { height, width } = input ?? element.getBoundingClientRect();
|
|
60
|
+
if (options.maximumWidth > -1 && width > options.maximumWidth) width = options.maximumWidth;
|
|
61
|
+
else if (options.minimumWidth > -1 && width < options.minimumWidth) width = options.minimumWidth;
|
|
62
|
+
if (options.maximumHeight > -1 && height > options.maximumHeight) height = options.maximumHeight;
|
|
63
|
+
else if (options.minimumHeight > -1 && height < options.minimumHeight) height = options.minimumHeight;
|
|
64
|
+
if (element.checkVisibility()) {
|
|
65
|
+
element.style.height = `${height}px`;
|
|
66
|
+
element.style.width = `${width}px`;
|
|
67
|
+
}
|
|
68
|
+
return [width, height];
|
|
69
|
+
}
|
|
70
|
+
function getProperties(element, widthProperty, heightProperty) {
|
|
71
|
+
const computed = getComputedStyle(element);
|
|
72
|
+
return [computed[widthProperty], computed[heightProperty]];
|
|
73
|
+
}
|
|
74
|
+
function getRestrictions(maximum, minimum) {
|
|
75
|
+
const [maximumWidth, maximumHeight] = getXAndY.apply(null, typeof maximum === "string" ? [maximum] : maximum);
|
|
76
|
+
const [minimumWidth, minimumHeight] = getXAndY.apply(null, typeof minimum === "string" ? [minimum] : minimum);
|
|
77
|
+
if (maximumWidth > -1 && minimumWidth > -1 && maximumWidth <= minimumWidth) throw new Error(MESSAGE_WIDTH);
|
|
78
|
+
if (maximumHeight > -1 && minimumHeight > -1 && maximumHeight <= minimumHeight) throw new Error(MESSAGE_HEIGHT);
|
|
79
|
+
return {
|
|
80
|
+
maximumHeight,
|
|
81
|
+
maximumWidth,
|
|
82
|
+
minimumHeight,
|
|
83
|
+
minimumWidth
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
function getState(element, handle, input) {
|
|
87
|
+
element.setAttribute(RESIZABLE_ATTRIBUTE, "");
|
|
88
|
+
const options = getValidOptions(element, input);
|
|
89
|
+
const [width, height] = getAndSetDimensions(element, options);
|
|
90
|
+
return {
|
|
91
|
+
element,
|
|
92
|
+
handle,
|
|
93
|
+
height,
|
|
94
|
+
options,
|
|
95
|
+
width,
|
|
96
|
+
styles: toggleStyles(element, styles)
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
function getValidOptions(element, input) {
|
|
100
|
+
const original = isPlainObject(input) ? input : {};
|
|
101
|
+
return { ...getRestrictions(input == null ? element.getAttribute(ATTRIBUTE_MAXIMUM) ?? getProperties(element, "maxWidth", "maxHeight") : [original.maximumWidth, original.maximumHeight], input == null ? element.getAttribute(ATTRIBUTE_MINIMUM) ?? getProperties(element, "minWidth", "minHeight") : [original.minimumWidth, original.minimumHeight]) };
|
|
102
|
+
}
|
|
103
|
+
function getXAndY(first, second) {
|
|
104
|
+
const [x, y] = typeof first === "string" && second == null ? first.split(",") : [first, second];
|
|
105
|
+
let xAsNumber = typeof x === "string" ? Number.parseFloat(x) : getNumber(x);
|
|
106
|
+
let yAsNumber = typeof y === "string" ? Number.parseFloat(y) : getNumber(y);
|
|
107
|
+
if (Number.isNaN(xAsNumber) || xAsNumber <= 0) xAsNumber = -1;
|
|
108
|
+
if (Number.isNaN(yAsNumber) || yAsNumber <= 0) yAsNumber = -1;
|
|
109
|
+
return [xAsNumber, yAsNumber];
|
|
110
|
+
}
|
|
111
|
+
function onKeydown(event) {
|
|
112
|
+
if (current == null || event.key !== "Escape") return;
|
|
113
|
+
current.state.element.style.height = `${current.state.height}px`;
|
|
114
|
+
current.state.element.style.width = `${current.state.width}px`;
|
|
115
|
+
resetResizable();
|
|
116
|
+
}
|
|
117
|
+
function onPointerdown(event) {
|
|
118
|
+
if (event.altKey || event.ctrlKey || event.metaKey || event.shiftKey || (supportsTouch.value ? event.type === "mousedown" : event.type === "touchstart" || event.button !== 0)) return;
|
|
119
|
+
const handle = findAncestor(event, SELECTOR_HANDLE);
|
|
120
|
+
const element = handle && findAncestor(handle, SELECTOR);
|
|
121
|
+
const stored = element && items.get(element);
|
|
122
|
+
const position = getPosition(event);
|
|
123
|
+
if (stored == null || position == null) return;
|
|
124
|
+
if (supportsTouch.value) event.preventDefault();
|
|
125
|
+
current = stored;
|
|
126
|
+
start = position;
|
|
127
|
+
current.state.styles.set();
|
|
128
|
+
styleToggler.set();
|
|
129
|
+
}
|
|
130
|
+
function onPointermove(event) {
|
|
131
|
+
if (current == null) return;
|
|
132
|
+
const { x, y } = getPosition(event) ?? {};
|
|
133
|
+
if (x == null || y == null) return;
|
|
134
|
+
const width = current.state.width + (x - (start?.x ?? 0));
|
|
135
|
+
const height = current.state.height + (y - (start?.y ?? 0));
|
|
136
|
+
getAndSetDimensions(current.state.element, current.state.options, {
|
|
137
|
+
height,
|
|
138
|
+
width
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
function onPointerup() {
|
|
142
|
+
if (current == null) return;
|
|
143
|
+
const { height, width } = current.state.element.getBoundingClientRect();
|
|
144
|
+
current.state.height = height;
|
|
145
|
+
current.state.width = width;
|
|
146
|
+
resetResizable();
|
|
147
|
+
}
|
|
148
|
+
function removeResizable(element) {
|
|
149
|
+
items.get(element)?.instance.destroy();
|
|
150
|
+
items.delete(element);
|
|
151
|
+
}
|
|
152
|
+
function resetResizable() {
|
|
153
|
+
current?.state.styles.remove();
|
|
154
|
+
styleToggler.remove();
|
|
155
|
+
current = void 0;
|
|
156
|
+
start = void 0;
|
|
157
|
+
}
|
|
158
|
+
const RESIZABLE_ATTRIBUTE = "oui-resizable";
|
|
159
|
+
const RESIZABLE_ATTRIBUTE_HANDLE = `${RESIZABLE_ATTRIBUTE}-handle`;
|
|
160
|
+
const ATTRIBUTE_MAXIMUM = `${RESIZABLE_ATTRIBUTE}-maximum`;
|
|
161
|
+
const ATTRIBUTE_MINIMUM = `${RESIZABLE_ATTRIBUTE}-minimum`;
|
|
162
|
+
const MESSAGE_ELEMENT = "The element must be an instance of HTMLElement or SVGElement";
|
|
163
|
+
const MESSAGE_HEIGHT = "The maximum height must be greater than the minimum height";
|
|
164
|
+
const MESSAGE_HANDLE = `The element must contain a child with the attribute "${RESIZABLE_ATTRIBUTE_HANDLE}"`;
|
|
165
|
+
const MESSAGE_WIDTH = "The maximum width must be greater than the minimum width";
|
|
166
|
+
const SELECTOR = `[${RESIZABLE_ATTRIBUTE}]`;
|
|
167
|
+
const SELECTOR_HANDLE = `[${RESIZABLE_ATTRIBUTE_HANDLE}]`;
|
|
168
|
+
const SELECTOR_HANDLE_SCOPED = `:scope > ${SELECTOR_HANDLE}`;
|
|
169
|
+
const items = /* @__PURE__ */ new WeakMap();
|
|
170
|
+
const styles = {
|
|
171
|
+
touchAction: "none",
|
|
172
|
+
userSelect: "none",
|
|
173
|
+
webkitUserSelect: "none"
|
|
174
|
+
};
|
|
175
|
+
const styleToggler = toggleStyles(document.body, styles);
|
|
176
|
+
let current;
|
|
177
|
+
let start;
|
|
178
|
+
on(document, "keydown", onKeydown);
|
|
179
|
+
on(document, "mousedown", onPointerdown);
|
|
180
|
+
on(document, "pointermove", onPointermove);
|
|
181
|
+
on(document, "pointerup", onPointerup);
|
|
182
|
+
on(document, "touchcancel", onPointerup);
|
|
183
|
+
on(document, "touchstart", onPointerdown, { passive: false });
|
|
184
|
+
attributable(RESIZABLE_ATTRIBUTE, addResizable, removeResizable);
|
|
185
|
+
//#endregion
|
|
186
|
+
export { OuiResizable, RESIZABLE_ATTRIBUTE, RESIZABLE_ATTRIBUTE_HANDLE, createResizable };
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { StyleToggler } from "@oscarpalmer/toretto/style";
|
|
2
|
+
|
|
3
|
+
//#region src/js/resizable/resizable.embedded.d.ts
|
|
4
|
+
type CreateOuiResizableOptions = {
|
|
5
|
+
maximumHeight?: number;
|
|
6
|
+
maximumWidth?: number;
|
|
7
|
+
minimumHeight?: number;
|
|
8
|
+
minimumWidth?: number;
|
|
9
|
+
};
|
|
10
|
+
declare class OuiResizable {
|
|
11
|
+
#private;
|
|
12
|
+
constructor(state: OuiResizableState);
|
|
13
|
+
destroy(): void;
|
|
14
|
+
set(): void;
|
|
15
|
+
reset(): void;
|
|
16
|
+
}
|
|
17
|
+
type OuiResizableState = {
|
|
18
|
+
element: HTMLElement;
|
|
19
|
+
handle: HTMLElement;
|
|
20
|
+
height: number;
|
|
21
|
+
options: Required<CreateOuiResizableOptions>;
|
|
22
|
+
styles: StyleToggler;
|
|
23
|
+
width: number;
|
|
24
|
+
};
|
|
25
|
+
/**
|
|
26
|
+
* Creates _(or retrieves)_ a _OuiResizable_ instance for an element
|
|
27
|
+
*
|
|
28
|
+
* @param element Element to make resizable
|
|
29
|
+
* @returns _OuiResizable_ instance
|
|
30
|
+
*/
|
|
31
|
+
declare function createResizable(element: HTMLElement, options?: CreateOuiResizableOptions): OuiResizable;
|
|
32
|
+
declare const RESIZABLE_ATTRIBUTE = "oui-resizable";
|
|
33
|
+
declare const RESIZABLE_ATTRIBUTE_HANDLE = "oui-resizable-handle";
|
|
34
|
+
//#endregion
|
|
35
|
+
export { createResizable as a, RESIZABLE_ATTRIBUTE_HANDLE as i, OuiResizable as n, RESIZABLE_ATTRIBUTE as r, CreateOuiResizableOptions as t };
|
package/dist/js/splitter.mjs
CHANGED
|
@@ -315,7 +315,7 @@ on(document, "keydown", onKeydown);
|
|
|
315
315
|
on(document, "mousedown", onPointerdown);
|
|
316
316
|
on(document, "pointermove", onPointermove);
|
|
317
317
|
on(document, "pointerup", onPointerup);
|
|
318
|
-
on(document, "touchcancel", onPointerup
|
|
318
|
+
on(document, "touchcancel", onPointerup);
|
|
319
319
|
on(document, "touchstart", onPointerdown, { passive: false });
|
|
320
320
|
//#endregion
|
|
321
321
|
export { OuiSplitterElement };
|
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",
|
|
@@ -53,6 +53,10 @@
|
|
|
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
61
|
"types": "./dist/js/sortable/sortable.standalone.d.mts",
|
|
58
62
|
"default": "./dist/js/sortable/sortable.standalone.mjs"
|
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
|
}
|
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/js/dialog.ts
CHANGED
|
@@ -4,6 +4,8 @@ import {getOnBeforeToggleListener} from './floatable/floatable.embedded';
|
|
|
4
4
|
import {attributable} from './internal/attributable';
|
|
5
5
|
import {ATTRIBUTE_MOVABLE_HANDLE} from './movable/movable.embedded';
|
|
6
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';
|
|
7
9
|
|
|
8
10
|
// #region Types
|
|
9
11
|
|
|
@@ -82,6 +84,7 @@ type OuiDialogState = {
|
|
|
82
84
|
listeners: RemovableEventListener[];
|
|
83
85
|
modal: boolean;
|
|
84
86
|
movable?: OuiMovable;
|
|
87
|
+
resizable?: OuiResizable;
|
|
85
88
|
};
|
|
86
89
|
|
|
87
90
|
// #endregion
|
|
@@ -108,9 +111,14 @@ function destroyDialog(state: OuiDialogState): void {
|
|
|
108
111
|
listener();
|
|
109
112
|
}
|
|
110
113
|
|
|
114
|
+
state.movable?.destroy();
|
|
115
|
+
state.resizable?.destroy();
|
|
116
|
+
|
|
111
117
|
state.dialog = undefined as never;
|
|
112
118
|
state.element = undefined as never;
|
|
113
119
|
state.listeners = undefined as never;
|
|
120
|
+
state.movable = undefined;
|
|
121
|
+
state.resizable = undefined;
|
|
114
122
|
}
|
|
115
123
|
|
|
116
124
|
/**
|
|
@@ -139,6 +147,18 @@ function getMovable(element: HTMLDialogElement): OuiMovable | undefined {
|
|
|
139
147
|
});
|
|
140
148
|
}
|
|
141
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
|
+
|
|
142
162
|
function getState(element: HTMLDialogElement): OuiDialogState {
|
|
143
163
|
const state: OuiDialogState = {
|
|
144
164
|
element,
|
|
@@ -157,6 +177,10 @@ function getState(element: HTMLDialogElement): OuiDialogState {
|
|
|
157
177
|
element.setAttribute(ATTRIBUTE_OPEN, '');
|
|
158
178
|
|
|
159
179
|
element.focus();
|
|
180
|
+
|
|
181
|
+
setTimeout(() => {
|
|
182
|
+
state.resizable?.set();
|
|
183
|
+
}, 500);
|
|
160
184
|
} else {
|
|
161
185
|
element.removeAttribute(ATTRIBUTE_OPEN);
|
|
162
186
|
element.setAttribute(ARIA_MODAL, String(state.modal));
|
|
@@ -164,6 +188,8 @@ function getState(element: HTMLDialogElement): OuiDialogState {
|
|
|
164
188
|
element.style.inset = '';
|
|
165
189
|
element.style.position = '';
|
|
166
190
|
element.style.transform = '';
|
|
191
|
+
|
|
192
|
+
state.resizable?.reset();
|
|
167
193
|
}
|
|
168
194
|
}),
|
|
169
195
|
],
|
|
@@ -171,7 +197,9 @@ function getState(element: HTMLDialogElement): OuiDialogState {
|
|
|
171
197
|
};
|
|
172
198
|
|
|
173
199
|
state.dialog = new OuiDialog(state);
|
|
200
|
+
|
|
174
201
|
state.movable = getMovable(element);
|
|
202
|
+
state.resizable = getResizable(element);
|
|
175
203
|
|
|
176
204
|
return state;
|
|
177
205
|
}
|
|
@@ -210,6 +238,8 @@ const SELECTOR_MOVER = `[${ATTRIBUTE}-mover]`;
|
|
|
210
238
|
|
|
211
239
|
const SELECTOR_MODAL = ':modal';
|
|
212
240
|
|
|
241
|
+
const SELECTOR_RESIZER = `[${ATTRIBUTE}-resizer]`;
|
|
242
|
+
|
|
213
243
|
const TRUE = 'true';
|
|
214
244
|
|
|
215
245
|
const states = new WeakMap<HTMLElement, OuiDialogState>();
|
package/src/js/index.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import './sortable/sortable.embedded';
|
|
2
1
|
import './toolbar';
|
|
3
2
|
|
|
4
3
|
export * from './dialog';
|
|
@@ -6,5 +5,7 @@ export * from './floatable/floatable.standalone';
|
|
|
6
5
|
export * from './focus-trap/focus-trap.standalone';
|
|
7
6
|
export * from './movable/movable.standalone';
|
|
8
7
|
export * from './popover';
|
|
8
|
+
export * from './resizable/resizable.standalone';
|
|
9
|
+
export * from './sortable/sortable.standalone';
|
|
9
10
|
export * from './splitter';
|
|
10
11
|
export * from './tooltip';
|
|
@@ -650,7 +650,7 @@ on(document, 'keydown', onKeydown);
|
|
|
650
650
|
on(document, 'mousedown', onPointerdown);
|
|
651
651
|
on(document, 'pointermove', onPointermove);
|
|
652
652
|
on(document, 'pointerup', onPointerup);
|
|
653
|
-
on(document, 'touchcancel', onPointerup
|
|
653
|
+
on(document, 'touchcancel', onPointerup);
|
|
654
654
|
on(document, 'touchstart', onPointerdown, {passive: false});
|
|
655
655
|
|
|
656
656
|
// #endregion
|
|
@@ -0,0 +1,383 @@
|
|
|
1
|
+
import {isPlainObject} from '@oscarpalmer/atoms/is';
|
|
2
|
+
import type {EventPosition} from '@oscarpalmer/atoms/models';
|
|
3
|
+
import {getNumber} from '@oscarpalmer/atoms/number';
|
|
4
|
+
import {getPosition, on} from '@oscarpalmer/toretto/event';
|
|
5
|
+
import {findAncestor} from '@oscarpalmer/toretto/find';
|
|
6
|
+
import {isHTMLOrSVGElement} from '@oscarpalmer/toretto/is';
|
|
7
|
+
import {toggleStyles, type StyleToggler} from '@oscarpalmer/toretto/style';
|
|
8
|
+
import supportsTouch from '@oscarpalmer/toretto/touch';
|
|
9
|
+
import {attributable} from '../internal/attributable';
|
|
10
|
+
|
|
11
|
+
// #region Types
|
|
12
|
+
|
|
13
|
+
export type CreateOuiResizableOptions = {
|
|
14
|
+
maximumHeight?: number;
|
|
15
|
+
maximumWidth?: number;
|
|
16
|
+
minimumHeight?: number;
|
|
17
|
+
minimumWidth?: number;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export class OuiResizable {
|
|
21
|
+
readonly #state: OuiResizableState;
|
|
22
|
+
|
|
23
|
+
constructor(state: OuiResizableState) {
|
|
24
|
+
this.#state = state;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
destroy(): void {
|
|
28
|
+
destroyResizable(this.#state);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
set(): void {
|
|
32
|
+
const [width, height] = getAndSetDimensions(this.#state.element, this.#state.options);
|
|
33
|
+
|
|
34
|
+
console.log('set', width, height);
|
|
35
|
+
|
|
36
|
+
this.#state.width = width;
|
|
37
|
+
this.#state.height = height;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
reset(): void {
|
|
41
|
+
this.#state.element.style.height = '';
|
|
42
|
+
this.#state.element.style.width = '';
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
type OuiResizableItem = {
|
|
47
|
+
instance: OuiResizable;
|
|
48
|
+
state: OuiResizableState;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
type OuiResizableState = {
|
|
52
|
+
element: HTMLElement;
|
|
53
|
+
handle: HTMLElement;
|
|
54
|
+
height: number;
|
|
55
|
+
options: Required<CreateOuiResizableOptions>;
|
|
56
|
+
styles: StyleToggler;
|
|
57
|
+
width: number;
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
// #endregion
|
|
61
|
+
|
|
62
|
+
// #region Functions
|
|
63
|
+
|
|
64
|
+
function addResizable(
|
|
65
|
+
element: HTMLElement,
|
|
66
|
+
options?: CreateOuiResizableOptions,
|
|
67
|
+
): OuiResizable | undefined {
|
|
68
|
+
let item = items.get(element);
|
|
69
|
+
|
|
70
|
+
if (item == null) {
|
|
71
|
+
const handle = element.querySelector(SELECTOR_HANDLE_SCOPED);
|
|
72
|
+
|
|
73
|
+
if (!isHTMLOrSVGElement(handle)) {
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const state = getState(element, handle, options);
|
|
78
|
+
const instance = new OuiResizable(state);
|
|
79
|
+
|
|
80
|
+
item = {
|
|
81
|
+
instance,
|
|
82
|
+
state,
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
items.set(element, item);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return item?.instance;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Creates _(or retrieves)_ a _OuiResizable_ instance for an element
|
|
93
|
+
*
|
|
94
|
+
* @param element Element to make resizable
|
|
95
|
+
* @returns _OuiResizable_ instance
|
|
96
|
+
*/
|
|
97
|
+
export function createResizable(
|
|
98
|
+
element: HTMLElement,
|
|
99
|
+
options?: CreateOuiResizableOptions,
|
|
100
|
+
): OuiResizable {
|
|
101
|
+
if (!isHTMLOrSVGElement(element)) {
|
|
102
|
+
throw new TypeError(MESSAGE_ELEMENT);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (!isHTMLOrSVGElement(element.querySelector(SELECTOR_HANDLE_SCOPED))) {
|
|
106
|
+
throw new Error(MESSAGE_HANDLE);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return addResizable(element, options ?? {})!;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function destroyResizable(state: OuiResizableState): void {
|
|
113
|
+
state.element = undefined as never;
|
|
114
|
+
state.handle = undefined as never;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function getAndSetDimensions(
|
|
118
|
+
element: HTMLElement,
|
|
119
|
+
options: Required<CreateOuiResizableOptions>,
|
|
120
|
+
input?: Pick<DOMRect, 'height' | 'width'>,
|
|
121
|
+
): [number, number] {
|
|
122
|
+
let {height, width} = input ?? element.getBoundingClientRect();
|
|
123
|
+
|
|
124
|
+
if (options.maximumWidth > -1 && width > options.maximumWidth) {
|
|
125
|
+
width = options.maximumWidth;
|
|
126
|
+
} else if (options.minimumWidth > -1 && width < options.minimumWidth) {
|
|
127
|
+
width = options.minimumWidth;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (options.maximumHeight > -1 && height > options.maximumHeight) {
|
|
131
|
+
height = options.maximumHeight;
|
|
132
|
+
} else if (options.minimumHeight > -1 && height < options.minimumHeight) {
|
|
133
|
+
height = options.minimumHeight;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (element.checkVisibility()) {
|
|
137
|
+
element.style.height = `${height}px`;
|
|
138
|
+
element.style.width = `${width}px`;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return [width, height];
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function getProperties(
|
|
145
|
+
element: HTMLElement,
|
|
146
|
+
widthProperty: 'maxWidth' | 'minWidth',
|
|
147
|
+
heightProperty: 'maxHeight' | 'minHeight',
|
|
148
|
+
): [string, string] {
|
|
149
|
+
const computed = getComputedStyle(element);
|
|
150
|
+
|
|
151
|
+
return [computed[widthProperty], computed[heightProperty]];
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function getRestrictions(
|
|
155
|
+
maximum: string | [unknown, unknown],
|
|
156
|
+
minimum: string | [unknown, unknown],
|
|
157
|
+
): Required<CreateOuiResizableOptions> {
|
|
158
|
+
const [maximumWidth, maximumHeight] = getXAndY.apply(
|
|
159
|
+
null,
|
|
160
|
+
typeof maximum === 'string' ? [maximum] : maximum,
|
|
161
|
+
);
|
|
162
|
+
|
|
163
|
+
const [minimumWidth, minimumHeight] = getXAndY.apply(
|
|
164
|
+
null,
|
|
165
|
+
typeof minimum === 'string' ? [minimum] : minimum,
|
|
166
|
+
);
|
|
167
|
+
|
|
168
|
+
if (maximumWidth > -1 && minimumWidth > -1 && maximumWidth <= minimumWidth) {
|
|
169
|
+
throw new Error(MESSAGE_WIDTH);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
if (maximumHeight > -1 && minimumHeight > -1 && maximumHeight <= minimumHeight) {
|
|
173
|
+
throw new Error(MESSAGE_HEIGHT);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
return {
|
|
177
|
+
maximumHeight,
|
|
178
|
+
maximumWidth,
|
|
179
|
+
minimumHeight,
|
|
180
|
+
minimumWidth,
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function getState(
|
|
185
|
+
element: HTMLElement,
|
|
186
|
+
handle: HTMLElement,
|
|
187
|
+
input?: CreateOuiResizableOptions,
|
|
188
|
+
): OuiResizableState {
|
|
189
|
+
element.setAttribute(RESIZABLE_ATTRIBUTE, '');
|
|
190
|
+
|
|
191
|
+
const options = getValidOptions(element, input);
|
|
192
|
+
|
|
193
|
+
const [width, height] = getAndSetDimensions(element, options);
|
|
194
|
+
|
|
195
|
+
return {
|
|
196
|
+
element,
|
|
197
|
+
handle,
|
|
198
|
+
height,
|
|
199
|
+
options,
|
|
200
|
+
width,
|
|
201
|
+
styles: toggleStyles(element, styles),
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function getValidOptions(
|
|
206
|
+
element: HTMLElement,
|
|
207
|
+
input?: CreateOuiResizableOptions,
|
|
208
|
+
): Required<CreateOuiResizableOptions> {
|
|
209
|
+
const original = isPlainObject(input) ? input : {};
|
|
210
|
+
|
|
211
|
+
return {
|
|
212
|
+
...getRestrictions(
|
|
213
|
+
input == null
|
|
214
|
+
? (element.getAttribute(ATTRIBUTE_MAXIMUM) ??
|
|
215
|
+
getProperties(element, 'maxWidth', 'maxHeight'))
|
|
216
|
+
: [original.maximumWidth, original.maximumHeight],
|
|
217
|
+
input == null
|
|
218
|
+
? (element.getAttribute(ATTRIBUTE_MINIMUM) ??
|
|
219
|
+
getProperties(element, 'minWidth', 'minHeight'))
|
|
220
|
+
: [original.minimumWidth, original.minimumHeight],
|
|
221
|
+
),
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
function getXAndY(first: unknown, second?: unknown): [number, number] {
|
|
226
|
+
const [x, y] = typeof first === 'string' && second == null ? first.split(',') : [first, second];
|
|
227
|
+
|
|
228
|
+
let xAsNumber = typeof x === 'string' ? Number.parseFloat(x) : getNumber(x);
|
|
229
|
+
let yAsNumber = typeof y === 'string' ? Number.parseFloat(y) : getNumber(y);
|
|
230
|
+
|
|
231
|
+
if (Number.isNaN(xAsNumber) || xAsNumber <= 0) {
|
|
232
|
+
xAsNumber = -1;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
if (Number.isNaN(yAsNumber) || yAsNumber <= 0) {
|
|
236
|
+
yAsNumber = -1;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
return [xAsNumber, yAsNumber];
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function onKeydown(event: KeyboardEvent): void {
|
|
243
|
+
if (current == null || event.key !== 'Escape') {
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
current.state.element.style.height = `${current.state.height}px`;
|
|
248
|
+
current.state.element.style.width = `${current.state.width}px`;
|
|
249
|
+
|
|
250
|
+
resetResizable();
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
function onPointerdown(event: MouseEvent | TouchEvent): void {
|
|
254
|
+
if (
|
|
255
|
+
event.altKey ||
|
|
256
|
+
event.ctrlKey ||
|
|
257
|
+
event.metaKey ||
|
|
258
|
+
event.shiftKey ||
|
|
259
|
+
(supportsTouch.value
|
|
260
|
+
? event.type === 'mousedown'
|
|
261
|
+
: event.type === 'touchstart' || (event as MouseEvent).button !== 0)
|
|
262
|
+
) {
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
const handle = findAncestor(event, SELECTOR_HANDLE);
|
|
267
|
+
const element = handle && findAncestor(handle, SELECTOR);
|
|
268
|
+
const stored = element && items.get(element as HTMLElement);
|
|
269
|
+
const position = getPosition(event);
|
|
270
|
+
|
|
271
|
+
if (stored == null || position == null) {
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
if (supportsTouch.value) {
|
|
276
|
+
event.preventDefault();
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
current = stored;
|
|
280
|
+
start = position;
|
|
281
|
+
|
|
282
|
+
current.state.styles.set();
|
|
283
|
+
styleToggler.set();
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
function onPointermove(event: MouseEvent | TouchEvent): void {
|
|
287
|
+
if (current == null) {
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
const {x, y} = getPosition(event) ?? {};
|
|
292
|
+
|
|
293
|
+
if (x == null || y == null) {
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
const width = current.state.width + (x - (start?.x ?? 0));
|
|
298
|
+
const height = current.state.height + (y - (start?.y ?? 0));
|
|
299
|
+
|
|
300
|
+
getAndSetDimensions(current.state.element, current.state.options, {height, width});
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
function onPointerup(): void {
|
|
304
|
+
if (current == null) {
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
const {height, width} = current.state.element.getBoundingClientRect();
|
|
309
|
+
|
|
310
|
+
current.state.height = height;
|
|
311
|
+
current.state.width = width;
|
|
312
|
+
|
|
313
|
+
resetResizable();
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
function removeResizable(element: HTMLElement): void {
|
|
317
|
+
items.get(element)?.instance.destroy();
|
|
318
|
+
|
|
319
|
+
items.delete(element);
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
function resetResizable(): void {
|
|
323
|
+
current?.state.styles.remove();
|
|
324
|
+
styleToggler.remove();
|
|
325
|
+
|
|
326
|
+
current = undefined;
|
|
327
|
+
start = undefined;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// #endregion
|
|
331
|
+
|
|
332
|
+
// #region Variables
|
|
333
|
+
|
|
334
|
+
export const RESIZABLE_ATTRIBUTE = 'oui-resizable';
|
|
335
|
+
|
|
336
|
+
export const RESIZABLE_ATTRIBUTE_HANDLE = `${RESIZABLE_ATTRIBUTE}-handle`;
|
|
337
|
+
|
|
338
|
+
const ATTRIBUTE_MAXIMUM = `${RESIZABLE_ATTRIBUTE}-maximum`;
|
|
339
|
+
|
|
340
|
+
const ATTRIBUTE_MINIMUM = `${RESIZABLE_ATTRIBUTE}-minimum`;
|
|
341
|
+
|
|
342
|
+
const MESSAGE_ELEMENT = 'The element must be an instance of HTMLElement or SVGElement';
|
|
343
|
+
|
|
344
|
+
const MESSAGE_HEIGHT = 'The maximum height must be greater than the minimum height';
|
|
345
|
+
|
|
346
|
+
const MESSAGE_HANDLE = `The element must contain a child with the attribute "${RESIZABLE_ATTRIBUTE_HANDLE}"`;
|
|
347
|
+
|
|
348
|
+
const MESSAGE_WIDTH = 'The maximum width must be greater than the minimum width';
|
|
349
|
+
|
|
350
|
+
const SELECTOR = `[${RESIZABLE_ATTRIBUTE}]`;
|
|
351
|
+
|
|
352
|
+
const SELECTOR_HANDLE = `[${RESIZABLE_ATTRIBUTE_HANDLE}]`;
|
|
353
|
+
|
|
354
|
+
const SELECTOR_HANDLE_SCOPED = `:scope > ${SELECTOR_HANDLE}`;
|
|
355
|
+
|
|
356
|
+
const items = new WeakMap<HTMLElement, OuiResizableItem>();
|
|
357
|
+
|
|
358
|
+
const styles = {
|
|
359
|
+
touchAction: 'none',
|
|
360
|
+
userSelect: 'none',
|
|
361
|
+
webkitUserSelect: 'none',
|
|
362
|
+
};
|
|
363
|
+
|
|
364
|
+
const styleToggler = toggleStyles(document.body, styles);
|
|
365
|
+
|
|
366
|
+
let current: OuiResizableItem | undefined;
|
|
367
|
+
|
|
368
|
+
let start: EventPosition | undefined;
|
|
369
|
+
|
|
370
|
+
// #endregion
|
|
371
|
+
|
|
372
|
+
// #region Initialization
|
|
373
|
+
|
|
374
|
+
on(document, 'keydown', onKeydown);
|
|
375
|
+
on(document, 'mousedown', onPointerdown);
|
|
376
|
+
on(document, 'pointermove', onPointermove);
|
|
377
|
+
on(document, 'pointerup', onPointerup);
|
|
378
|
+
on(document, 'touchcancel', onPointerup);
|
|
379
|
+
on(document, 'touchstart', onPointerdown, {passive: false});
|
|
380
|
+
|
|
381
|
+
attributable(RESIZABLE_ATTRIBUTE, addResizable, removeResizable);
|
|
382
|
+
|
|
383
|
+
// #endregion
|
package/src/js/splitter.ts
CHANGED
|
@@ -557,5 +557,5 @@ on(document, 'keydown', onKeydown);
|
|
|
557
557
|
on(document, 'mousedown', onPointerdown);
|
|
558
558
|
on(document, 'pointermove', onPointermove);
|
|
559
559
|
on(document, 'pointerup', onPointerup);
|
|
560
|
-
on(document, 'touchcancel', onPointerup
|
|
560
|
+
on(document, 'touchcancel', onPointerup);
|
|
561
561
|
on(document, 'touchstart', onPointerdown, {passive: false});
|