@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
|
@@ -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
|