@oscarpalmer/oui 0.27.0 → 0.29.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 +3 -1
- package/dist/js/dialog.mjs +35 -19
- package/dist/js/{draggable-zszaW1qS.d.mts → draggable-fIYXd8eO.d.mts} +3 -3
- package/dist/js/floatable/floatable.embedded.d.mts +2 -2
- package/dist/js/floatable/floatable.embedded.mjs +17 -16
- package/dist/js/floatable/floatable.standalone.d.mts +1 -1
- package/dist/js/{floatable.embedded-YxGwzgbE.d.mts → floatable.embedded-DUR8bQBK.d.mts} +1 -2
- package/dist/js/focus-trap/focus-trap.embedded.mjs +6 -5
- package/dist/js/index.d.mts +4 -3
- package/dist/js/index.mjs +6 -2
- package/dist/js/internal/attributable.mjs +3 -4
- package/dist/js/internal/draggable.d.mts +1 -1
- package/dist/js/internal/draggable.mjs +12 -9
- package/dist/js/internal/navigable.d.mts +32 -0
- package/dist/js/internal/navigable.mjs +107 -0
- package/dist/js/menu.d.mts +1 -0
- package/dist/js/menu.mjs +168 -0
- package/dist/js/movable/movable.embedded.d.mts +1 -1
- package/dist/js/movable/movable.embedded.mjs +8 -13
- package/dist/js/movable/movable.standalone.d.mts +1 -1
- package/dist/js/popover.d.mts +1 -1
- package/dist/js/popover.mjs +27 -12
- package/dist/js/resizable/resizable.embedded.d.mts +2 -0
- package/dist/js/resizable/resizable.embedded.mjs +191 -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 +1 -1
- package/dist/js/sortable/sortable.embedded.mjs +15 -13
- package/dist/js/sortable/sortable.standalone.d.mts +1 -1
- package/dist/js/splitter.mjs +30 -26
- package/dist/js/toolbar.mjs +12 -88
- package/dist/js/tooltip.d.mts +1 -1
- package/dist/js/tooltip.mjs +12 -17
- package/package.json +11 -3
- package/src/css/floatable.scss +9 -0
- package/src/css/form.scss +6 -6
- package/src/js/dialog.ts +50 -24
- package/src/js/floatable/floatable.embedded.ts +19 -18
- package/src/js/focus-trap/focus-trap.embedded.ts +6 -5
- package/src/js/index.ts +3 -1
- package/src/js/internal/attributable.ts +3 -5
- package/src/js/internal/draggable.ts +13 -10
- package/src/js/internal/navigable.ts +247 -0
- package/src/js/menu.ts +345 -0
- package/src/js/movable/movable.embedded.ts +10 -13
- package/src/js/popover.ts +41 -15
- package/src/js/resizable/resizable.embedded.ts +390 -0
- package/src/js/resizable/resizable.standalone.ts +5 -0
- package/src/js/sortable/sortable.embedded.ts +17 -22
- package/src/js/splitter.ts +41 -36
- package/src/js/toolbar.ts +27 -178
- package/src/js/tooltip.ts +18 -30
|
@@ -0,0 +1,390 @@
|
|
|
1
|
+
import {isPlainObject} from '@oscarpalmer/atoms/is';
|
|
2
|
+
import type {EventPosition} from '@oscarpalmer/atoms/models';
|
|
3
|
+
import {getNumber} from '@oscarpalmer/atoms/number';
|
|
4
|
+
import {getAttribute, setAttribute} from '@oscarpalmer/toretto/attribute';
|
|
5
|
+
import {getPosition, on} from '@oscarpalmer/toretto/event';
|
|
6
|
+
import {findAncestor} from '@oscarpalmer/toretto/find';
|
|
7
|
+
import {isHTMLOrSVGElement} from '@oscarpalmer/toretto/is';
|
|
8
|
+
import {setStyles, toggleStyles, type StyleToggler} from '@oscarpalmer/toretto/style';
|
|
9
|
+
import supportsTouch from '@oscarpalmer/toretto/touch';
|
|
10
|
+
import {attributable} from '../internal/attributable';
|
|
11
|
+
|
|
12
|
+
// #region Types
|
|
13
|
+
|
|
14
|
+
export type CreateOuiResizableOptions = {
|
|
15
|
+
maximumHeight?: number;
|
|
16
|
+
maximumWidth?: number;
|
|
17
|
+
minimumHeight?: number;
|
|
18
|
+
minimumWidth?: number;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export class OuiResizable {
|
|
22
|
+
readonly #state: OuiResizableState;
|
|
23
|
+
|
|
24
|
+
constructor(state: OuiResizableState) {
|
|
25
|
+
this.#state = state;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
destroy(): void {
|
|
29
|
+
destroyResizable(this.#state);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
set(): void {
|
|
33
|
+
const [width, height] = getAndSetDimensions(this.#state.element, this.#state.options);
|
|
34
|
+
|
|
35
|
+
console.log('set', width, height);
|
|
36
|
+
|
|
37
|
+
this.#state.width = width;
|
|
38
|
+
this.#state.height = height;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
reset(): void {
|
|
42
|
+
setStyles(this.#state.element, {
|
|
43
|
+
height: '',
|
|
44
|
+
width: '',
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
type OuiResizableItem = {
|
|
50
|
+
instance: OuiResizable;
|
|
51
|
+
state: OuiResizableState;
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
type OuiResizableState = {
|
|
55
|
+
element: HTMLElement;
|
|
56
|
+
handle: HTMLElement;
|
|
57
|
+
height: number;
|
|
58
|
+
options: Required<CreateOuiResizableOptions>;
|
|
59
|
+
styles: StyleToggler;
|
|
60
|
+
width: number;
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
// #endregion
|
|
64
|
+
|
|
65
|
+
// #region Functions
|
|
66
|
+
|
|
67
|
+
function addResizable(
|
|
68
|
+
element: HTMLElement,
|
|
69
|
+
options?: CreateOuiResizableOptions,
|
|
70
|
+
): OuiResizable | undefined {
|
|
71
|
+
let item = items.get(element);
|
|
72
|
+
|
|
73
|
+
if (item == null) {
|
|
74
|
+
const handle = element.querySelector(SELECTOR_HANDLE_SCOPED);
|
|
75
|
+
|
|
76
|
+
if (!isHTMLOrSVGElement(handle)) {
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const state = getState(element, handle, options);
|
|
81
|
+
const instance = new OuiResizable(state);
|
|
82
|
+
|
|
83
|
+
item = {
|
|
84
|
+
instance,
|
|
85
|
+
state,
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
items.set(element, item);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return item?.instance;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Creates _(or retrieves)_ a _OuiResizable_ instance for an element
|
|
96
|
+
*
|
|
97
|
+
* @param element Element to make resizable
|
|
98
|
+
* @returns _OuiResizable_ instance
|
|
99
|
+
*/
|
|
100
|
+
export function createResizable(
|
|
101
|
+
element: HTMLElement,
|
|
102
|
+
options?: CreateOuiResizableOptions,
|
|
103
|
+
): OuiResizable {
|
|
104
|
+
if (!isHTMLOrSVGElement(element)) {
|
|
105
|
+
throw new TypeError(MESSAGE_ELEMENT);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (!isHTMLOrSVGElement(element.querySelector(SELECTOR_HANDLE_SCOPED))) {
|
|
109
|
+
throw new Error(MESSAGE_HANDLE);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return addResizable(element, options ?? {})!;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function destroyResizable(state: OuiResizableState): void {
|
|
116
|
+
state.element = undefined as never;
|
|
117
|
+
state.handle = undefined as never;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function getAndSetDimensions(
|
|
121
|
+
element: HTMLElement,
|
|
122
|
+
options: Required<CreateOuiResizableOptions>,
|
|
123
|
+
input?: Pick<DOMRect, 'height' | 'width'>,
|
|
124
|
+
): [number, number] {
|
|
125
|
+
let {height, width} = input ?? element.getBoundingClientRect();
|
|
126
|
+
|
|
127
|
+
if (options.maximumWidth > -1 && width > options.maximumWidth) {
|
|
128
|
+
width = options.maximumWidth;
|
|
129
|
+
} else if (options.minimumWidth > -1 && width < options.minimumWidth) {
|
|
130
|
+
width = options.minimumWidth;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (options.maximumHeight > -1 && height > options.maximumHeight) {
|
|
134
|
+
height = options.maximumHeight;
|
|
135
|
+
} else if (options.minimumHeight > -1 && height < options.minimumHeight) {
|
|
136
|
+
height = options.minimumHeight;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if (element.checkVisibility()) {
|
|
140
|
+
setStyles(element, {
|
|
141
|
+
height: `${height}px`,
|
|
142
|
+
width: `${width}px`,
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return [width, height];
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function getProperties(
|
|
150
|
+
element: HTMLElement,
|
|
151
|
+
widthProperty: 'maxWidth' | 'minWidth',
|
|
152
|
+
heightProperty: 'maxHeight' | 'minHeight',
|
|
153
|
+
): [string, string] {
|
|
154
|
+
const computed = getComputedStyle(element);
|
|
155
|
+
|
|
156
|
+
return [computed[widthProperty], computed[heightProperty]];
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function getRestrictions(
|
|
160
|
+
maximum: string | [unknown, unknown],
|
|
161
|
+
minimum: string | [unknown, unknown],
|
|
162
|
+
): Required<CreateOuiResizableOptions> {
|
|
163
|
+
const [maximumWidth, maximumHeight] = getXAndY.apply(
|
|
164
|
+
null,
|
|
165
|
+
typeof maximum === 'string' ? [maximum] : maximum,
|
|
166
|
+
);
|
|
167
|
+
|
|
168
|
+
const [minimumWidth, minimumHeight] = getXAndY.apply(
|
|
169
|
+
null,
|
|
170
|
+
typeof minimum === 'string' ? [minimum] : minimum,
|
|
171
|
+
);
|
|
172
|
+
|
|
173
|
+
if (maximumWidth > -1 && minimumWidth > -1 && maximumWidth <= minimumWidth) {
|
|
174
|
+
throw new Error(MESSAGE_WIDTH);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
if (maximumHeight > -1 && minimumHeight > -1 && maximumHeight <= minimumHeight) {
|
|
178
|
+
throw new Error(MESSAGE_HEIGHT);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
return {
|
|
182
|
+
maximumHeight,
|
|
183
|
+
maximumWidth,
|
|
184
|
+
minimumHeight,
|
|
185
|
+
minimumWidth,
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function getState(
|
|
190
|
+
element: HTMLElement,
|
|
191
|
+
handle: HTMLElement,
|
|
192
|
+
input?: CreateOuiResizableOptions,
|
|
193
|
+
): OuiResizableState {
|
|
194
|
+
setAttribute(element, RESIZABLE_ATTRIBUTE, '');
|
|
195
|
+
|
|
196
|
+
const options = getValidOptions(element, input);
|
|
197
|
+
|
|
198
|
+
const [width, height] = getAndSetDimensions(element, options);
|
|
199
|
+
|
|
200
|
+
return {
|
|
201
|
+
element,
|
|
202
|
+
handle,
|
|
203
|
+
height,
|
|
204
|
+
options,
|
|
205
|
+
width,
|
|
206
|
+
styles: toggleStyles(element, styles),
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function getValidOptions(
|
|
211
|
+
element: HTMLElement,
|
|
212
|
+
input?: CreateOuiResizableOptions,
|
|
213
|
+
): Required<CreateOuiResizableOptions> {
|
|
214
|
+
const original = isPlainObject(input) ? input : {};
|
|
215
|
+
|
|
216
|
+
return {
|
|
217
|
+
...getRestrictions(
|
|
218
|
+
input == null
|
|
219
|
+
? (getAttribute(element, ATTRIBUTE_MAXIMUM) ??
|
|
220
|
+
getProperties(element, 'maxWidth', 'maxHeight'))
|
|
221
|
+
: [original.maximumWidth, original.maximumHeight],
|
|
222
|
+
input == null
|
|
223
|
+
? (getAttribute(element, ATTRIBUTE_MINIMUM) ??
|
|
224
|
+
getProperties(element, 'minWidth', 'minHeight'))
|
|
225
|
+
: [original.minimumWidth, original.minimumHeight],
|
|
226
|
+
),
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
function getXAndY(first: unknown, second?: unknown): [number, number] {
|
|
231
|
+
const [x, y] = typeof first === 'string' && second == null ? first.split(',') : [first, second];
|
|
232
|
+
|
|
233
|
+
let xAsNumber = typeof x === 'string' ? Number.parseFloat(x) : getNumber(x);
|
|
234
|
+
let yAsNumber = typeof y === 'string' ? Number.parseFloat(y) : getNumber(y);
|
|
235
|
+
|
|
236
|
+
if (Number.isNaN(xAsNumber) || xAsNumber <= 0) {
|
|
237
|
+
xAsNumber = -1;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
if (Number.isNaN(yAsNumber) || yAsNumber <= 0) {
|
|
241
|
+
yAsNumber = -1;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
return [xAsNumber, yAsNumber];
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
function onKeydown(event: KeyboardEvent): void {
|
|
248
|
+
if (current == null || event.key !== 'Escape') {
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
setStyles(current.state.element, {
|
|
253
|
+
height: `${current.state.height}px`,
|
|
254
|
+
width: `${current.state.width}px`,
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
resetResizable();
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
function onPointerdown(event: MouseEvent | TouchEvent): void {
|
|
261
|
+
if (
|
|
262
|
+
event.altKey ||
|
|
263
|
+
event.ctrlKey ||
|
|
264
|
+
event.metaKey ||
|
|
265
|
+
event.shiftKey ||
|
|
266
|
+
(supportsTouch.value
|
|
267
|
+
? event.type === 'mousedown'
|
|
268
|
+
: event.type === 'touchstart' || (event as MouseEvent).button !== 0)
|
|
269
|
+
) {
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
const handle = findAncestor(event, SELECTOR_HANDLE);
|
|
274
|
+
const element = handle && findAncestor(handle, SELECTOR);
|
|
275
|
+
const stored = element && items.get(element as HTMLElement);
|
|
276
|
+
const position = getPosition(event);
|
|
277
|
+
|
|
278
|
+
if (stored == null || position == null) {
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
if (supportsTouch.value) {
|
|
283
|
+
event.preventDefault();
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
current = stored;
|
|
287
|
+
start = position;
|
|
288
|
+
|
|
289
|
+
current.state.styles.set();
|
|
290
|
+
styleToggler.set();
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
function onPointermove(event: MouseEvent | TouchEvent): void {
|
|
294
|
+
if (current == null) {
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
const {x, y} = getPosition(event) ?? {};
|
|
299
|
+
|
|
300
|
+
if (x == null || y == null) {
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
const width = current.state.width + (x - (start?.x ?? 0));
|
|
305
|
+
const height = current.state.height + (y - (start?.y ?? 0));
|
|
306
|
+
|
|
307
|
+
getAndSetDimensions(current.state.element, current.state.options, {height, width});
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
function onPointerup(): void {
|
|
311
|
+
if (current == null) {
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
const {height, width} = current.state.element.getBoundingClientRect();
|
|
316
|
+
|
|
317
|
+
current.state.height = height;
|
|
318
|
+
current.state.width = width;
|
|
319
|
+
|
|
320
|
+
resetResizable();
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
function removeResizable(element: HTMLElement): void {
|
|
324
|
+
items.get(element)?.instance.destroy();
|
|
325
|
+
|
|
326
|
+
items.delete(element);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
function resetResizable(): void {
|
|
330
|
+
current?.state.styles.remove();
|
|
331
|
+
styleToggler.remove();
|
|
332
|
+
|
|
333
|
+
current = undefined;
|
|
334
|
+
start = undefined;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// #endregion
|
|
338
|
+
|
|
339
|
+
// #region Variables
|
|
340
|
+
|
|
341
|
+
export const RESIZABLE_ATTRIBUTE = 'oui-resizable';
|
|
342
|
+
|
|
343
|
+
export const RESIZABLE_ATTRIBUTE_HANDLE = `${RESIZABLE_ATTRIBUTE}-handle`;
|
|
344
|
+
|
|
345
|
+
const ATTRIBUTE_MAXIMUM = `${RESIZABLE_ATTRIBUTE}-maximum`;
|
|
346
|
+
|
|
347
|
+
const ATTRIBUTE_MINIMUM = `${RESIZABLE_ATTRIBUTE}-minimum`;
|
|
348
|
+
|
|
349
|
+
const MESSAGE_ELEMENT = 'The element must be an instance of HTMLElement or SVGElement';
|
|
350
|
+
|
|
351
|
+
const MESSAGE_HEIGHT = 'The maximum height must be greater than the minimum height';
|
|
352
|
+
|
|
353
|
+
const MESSAGE_HANDLE = `The element must contain a child with the attribute "${RESIZABLE_ATTRIBUTE_HANDLE}"`;
|
|
354
|
+
|
|
355
|
+
const MESSAGE_WIDTH = 'The maximum width must be greater than the minimum width';
|
|
356
|
+
|
|
357
|
+
const SELECTOR = `[${RESIZABLE_ATTRIBUTE}]`;
|
|
358
|
+
|
|
359
|
+
const SELECTOR_HANDLE = `[${RESIZABLE_ATTRIBUTE_HANDLE}]`;
|
|
360
|
+
|
|
361
|
+
const SELECTOR_HANDLE_SCOPED = `:scope > ${SELECTOR_HANDLE}`;
|
|
362
|
+
|
|
363
|
+
const items = new WeakMap<HTMLElement, OuiResizableItem>();
|
|
364
|
+
|
|
365
|
+
const styles = {
|
|
366
|
+
touchAction: 'none',
|
|
367
|
+
userSelect: 'none',
|
|
368
|
+
webkitUserSelect: 'none',
|
|
369
|
+
};
|
|
370
|
+
|
|
371
|
+
const styleToggler = toggleStyles(document.body, styles);
|
|
372
|
+
|
|
373
|
+
let current: OuiResizableItem | undefined;
|
|
374
|
+
|
|
375
|
+
let start: EventPosition | undefined;
|
|
376
|
+
|
|
377
|
+
// #endregion
|
|
378
|
+
|
|
379
|
+
// #region Initialization
|
|
380
|
+
|
|
381
|
+
on(document, 'keydown', onKeydown);
|
|
382
|
+
on(document, 'mousedown', onPointerdown);
|
|
383
|
+
on(document, 'pointermove', onPointermove);
|
|
384
|
+
on(document, 'pointerup', onPointerup);
|
|
385
|
+
on(document, 'touchcancel', onPointerup);
|
|
386
|
+
on(document, 'touchstart', onPointerdown, {passive: false});
|
|
387
|
+
|
|
388
|
+
attributable(RESIZABLE_ATTRIBUTE, addResizable, removeResizable);
|
|
389
|
+
|
|
390
|
+
// #endregion
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import type {EventPosition, PlainObject} from '@oscarpalmer/atoms/models';
|
|
2
|
+
import {getAttribute, setAttribute, setAttributes} from '@oscarpalmer/toretto/attribute';
|
|
2
3
|
import {createElement} from '@oscarpalmer/toretto/create';
|
|
3
4
|
import {getPosition} from '@oscarpalmer/toretto/event';
|
|
4
5
|
import {findAncestor, getElementFromPosition} from '@oscarpalmer/toretto/find';
|
|
5
6
|
import {isHTMLOrSVGElement} from '@oscarpalmer/toretto/is';
|
|
7
|
+
import {setStyle} from '@oscarpalmer/toretto/style';
|
|
6
8
|
import {attributable} from '../internal/attributable';
|
|
7
9
|
import {
|
|
8
10
|
addDraggable,
|
|
@@ -50,7 +52,7 @@ export class OuiSortable extends OuiDraggable<OuiSortableState> {
|
|
|
50
52
|
(getDraggableStates(element)?.sortable as OuiDraggableState & OuiSortableState)?.origin,
|
|
51
53
|
},
|
|
52
54
|
{
|
|
53
|
-
connections:
|
|
55
|
+
connections: getAttribute(element, ATTRIBUTE_CONNECTIONS),
|
|
54
56
|
},
|
|
55
57
|
);
|
|
56
58
|
}
|
|
@@ -67,7 +69,7 @@ type OuiSortablePlaceholderDrag = {
|
|
|
67
69
|
};
|
|
68
70
|
|
|
69
71
|
type OuiSortableState = {
|
|
70
|
-
connections: string | null;
|
|
72
|
+
connections: string | null | undefined;
|
|
71
73
|
origin?: HTMLElement;
|
|
72
74
|
};
|
|
73
75
|
|
|
@@ -166,10 +168,11 @@ function getOriginalPosition(
|
|
|
166
168
|
function getPositionPlaceholder(item: OuiDraggableItem<OuiSortableState>): HTMLElement {
|
|
167
169
|
placeholder.position ??= createElement('div');
|
|
168
170
|
|
|
169
|
-
placeholder.position
|
|
171
|
+
setAttribute(placeholder.position, ATTRIBUTE_PLACEHOLDER_POSITION, '');
|
|
170
172
|
|
|
171
173
|
if (item.state.origin != null) {
|
|
172
|
-
|
|
174
|
+
setStyle(
|
|
175
|
+
placeholder.position,
|
|
173
176
|
'--placeholder-height',
|
|
174
177
|
`${item.state.origin.getBoundingClientRect().height}px`,
|
|
175
178
|
);
|
|
@@ -188,9 +191,9 @@ function onBegin(
|
|
|
188
191
|
|
|
189
192
|
setPlaceholder(globals, item, element);
|
|
190
193
|
|
|
191
|
-
|
|
194
|
+
setAttribute(element, ATTRIBUTE_ORIGIN, '');
|
|
192
195
|
|
|
193
|
-
document.body
|
|
196
|
+
setAttribute(document.body, ATTRIBUTE_ACTIVE, '');
|
|
194
197
|
}
|
|
195
198
|
|
|
196
199
|
function onCancel(_: OuiDraggableGlobals, item: OuiDraggableItem<OuiSortableState>): void {
|
|
@@ -218,7 +221,7 @@ function onEnd(
|
|
|
218
221
|
let element = findAncestor(target, SELECTOR_ITEM) as HTMLElement;
|
|
219
222
|
|
|
220
223
|
if (element?.hasAttribute(ATTRIBUTE_PLACEHOLDER_POSITION)) {
|
|
221
|
-
const position =
|
|
224
|
+
const position = getAttribute(element, ATTRIBUTE_PLACEHOLDER_POSITION);
|
|
222
225
|
|
|
223
226
|
if (position === 'after') {
|
|
224
227
|
element = element.previousElementSibling as HTMLElement;
|
|
@@ -344,7 +347,7 @@ function onMove(
|
|
|
344
347
|
toElement.insertAdjacentElement(INSERT_AFTER, placeholder);
|
|
345
348
|
}
|
|
346
349
|
|
|
347
|
-
|
|
350
|
+
setAttribute(placeholder, ATTRIBUTE_PLACEHOLDER_POSITION, place);
|
|
348
351
|
} else {
|
|
349
352
|
placeholder.remove();
|
|
350
353
|
}
|
|
@@ -417,9 +420,8 @@ function setPlaceholder(
|
|
|
417
420
|
}
|
|
418
421
|
|
|
419
422
|
function updatePlaceholder(item: OuiDraggableItem<OuiSortableState>, element: HTMLElement): void {
|
|
420
|
-
element
|
|
421
|
-
|
|
422
|
-
element.setAttribute(ATTRIBUTE_PLACEHOLDER_DRAG, '');
|
|
423
|
+
setAttribute(element, ATTRIBUTE_PLACEHOLDER_DRAG, '');
|
|
424
|
+
setStyle(element, 'position', 'fixed');
|
|
423
425
|
|
|
424
426
|
const state = getDraggableStates(item.state.element)?.sortable;
|
|
425
427
|
|
|
@@ -427,17 +429,10 @@ function updatePlaceholder(item: OuiDraggableItem<OuiSortableState>, element: HT
|
|
|
427
429
|
return;
|
|
428
430
|
}
|
|
429
431
|
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
}
|
|
435
|
-
|
|
436
|
-
if (state.vertical) {
|
|
437
|
-
element.setAttribute(ATTRIBUTE_VERTICAL, '');
|
|
438
|
-
} else {
|
|
439
|
-
element.removeAttribute(ATTRIBUTE_VERTICAL);
|
|
440
|
-
}
|
|
432
|
+
setAttributes(element, {
|
|
433
|
+
[ATTRIBUTE_HORIZONTAL]: state.horizontal ? '' : undefined,
|
|
434
|
+
[ATTRIBUTE_VERTICAL]: state.vertical ? '' : undefined,
|
|
435
|
+
});
|
|
441
436
|
}
|
|
442
437
|
|
|
443
438
|
function validatePosition(
|
package/src/js/splitter.ts
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import {isNullableOrWhitespace} from '@oscarpalmer/atoms/is';
|
|
2
2
|
import {round} from '@oscarpalmer/atoms/math';
|
|
3
3
|
import {clamp} from '@oscarpalmer/atoms/number';
|
|
4
|
+
import {setAria} from '@oscarpalmer/toretto/aria';
|
|
5
|
+
import {getAttribute, setAttribute} from '@oscarpalmer/toretto/attribute';
|
|
4
6
|
import {getPosition, on} from '@oscarpalmer/toretto/event';
|
|
5
7
|
import {findAncestor} from '@oscarpalmer/toretto/find';
|
|
6
|
-
import {type StyleToggler, toggleStyles} from '@oscarpalmer/toretto/style';
|
|
8
|
+
import {setStyle, type StyleToggler, toggleStyles} from '@oscarpalmer/toretto/style';
|
|
7
9
|
import supportsTouch from '@oscarpalmer/toretto/touch';
|
|
8
10
|
|
|
9
11
|
declare global {
|
|
@@ -78,11 +80,7 @@ class OuiSplitter {
|
|
|
78
80
|
RESIZE_OBSERVER.unobserve(this.element);
|
|
79
81
|
}
|
|
80
82
|
|
|
81
|
-
|
|
82
|
-
this.element.setAttribute(ATTRIBUTE_HORIZONTAL, '');
|
|
83
|
-
} else {
|
|
84
|
-
this.element.removeAttribute(ATTRIBUTE_HORIZONTAL);
|
|
85
|
-
}
|
|
83
|
+
setAttribute(this.element, ATTRIBUTE_HORIZONTAL, this.types.horizontal ? '' : undefined);
|
|
86
84
|
|
|
87
85
|
setAriaOrientation(this);
|
|
88
86
|
}
|
|
@@ -120,7 +118,9 @@ export class OuiSplitterElement extends HTMLElement {
|
|
|
120
118
|
* Maximum size for the first panel
|
|
121
119
|
*/
|
|
122
120
|
set max(value: number) {
|
|
123
|
-
|
|
121
|
+
if (typeof value === 'number') {
|
|
122
|
+
setAttribute(this, ATTRIBUTE_MAX, value.toString());
|
|
123
|
+
}
|
|
124
124
|
}
|
|
125
125
|
|
|
126
126
|
/**
|
|
@@ -134,7 +134,9 @@ export class OuiSplitterElement extends HTMLElement {
|
|
|
134
134
|
* Minimum size for the first panel
|
|
135
135
|
*/
|
|
136
136
|
set min(value: number) {
|
|
137
|
-
|
|
137
|
+
if (typeof value === 'number') {
|
|
138
|
+
setAttribute(this, ATTRIBUTE_MIN, value.toString());
|
|
139
|
+
}
|
|
138
140
|
}
|
|
139
141
|
|
|
140
142
|
/**
|
|
@@ -148,21 +150,23 @@ export class OuiSplitterElement extends HTMLElement {
|
|
|
148
150
|
* Current size for the first panel
|
|
149
151
|
*/
|
|
150
152
|
set size(value: number) {
|
|
151
|
-
|
|
153
|
+
if (typeof value === 'number') {
|
|
154
|
+
setAttribute(this, ATTRIBUTE_SIZE, value.toString());
|
|
155
|
+
}
|
|
152
156
|
}
|
|
153
157
|
|
|
154
158
|
/**
|
|
155
159
|
* Orientation of the splitter
|
|
156
160
|
*/
|
|
157
161
|
get type(): string {
|
|
158
|
-
return
|
|
162
|
+
return getAttribute(this, ATTRIBUTE_TYPE) ?? 'vertical';
|
|
159
163
|
}
|
|
160
164
|
|
|
161
165
|
/**
|
|
162
166
|
* Orientation of the splitter
|
|
163
167
|
*/
|
|
164
168
|
set type(value: string) {
|
|
165
|
-
|
|
169
|
+
setAttribute(this, ATTRIBUTE_TYPE, value);
|
|
166
170
|
}
|
|
167
171
|
|
|
168
172
|
// Constructor
|
|
@@ -170,7 +174,7 @@ export class OuiSplitterElement extends HTMLElement {
|
|
|
170
174
|
constructor() {
|
|
171
175
|
super();
|
|
172
176
|
|
|
173
|
-
const panels = [...this.children].filter(child => child instanceof HTMLElement
|
|
177
|
+
const panels = [...this.children].filter(child => child instanceof Element) as HTMLElement[];
|
|
174
178
|
|
|
175
179
|
if (panels.length !== 2) {
|
|
176
180
|
throw new Error(MESSAGE);
|
|
@@ -228,8 +232,8 @@ type OuiSplitterValuesNow = {
|
|
|
228
232
|
function createHandle(): HTMLSpanElement {
|
|
229
233
|
const handle = document.createElement('span');
|
|
230
234
|
|
|
231
|
-
handle
|
|
232
|
-
|
|
235
|
+
setAria(handle, 'hidden', 'true');
|
|
236
|
+
setAttribute(handle, ATTRIBUTE_HANDLE, '');
|
|
233
237
|
|
|
234
238
|
return handle;
|
|
235
239
|
}
|
|
@@ -240,9 +244,12 @@ function createSeparator(splitter: OuiSplitter): HTMLDivElement {
|
|
|
240
244
|
separator.role = 'separator';
|
|
241
245
|
separator.tabIndex = 0;
|
|
242
246
|
|
|
243
|
-
separator
|
|
244
|
-
|
|
245
|
-
|
|
247
|
+
setAria(separator, {
|
|
248
|
+
controls: splitter.element.id,
|
|
249
|
+
label: 'Resize panels',
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
setAttribute(separator, ATTRIBUTE_SEPARATOR, '');
|
|
246
253
|
|
|
247
254
|
return separator;
|
|
248
255
|
}
|
|
@@ -262,7 +269,7 @@ function getSize(splitter: OuiSplitter, value: number): number {
|
|
|
262
269
|
}
|
|
263
270
|
|
|
264
271
|
function getTypes(splitter: OuiSplitter, width?: number, height?: number): OuiSplitterTypes {
|
|
265
|
-
const type = splitter.element
|
|
272
|
+
const type = getAttribute(splitter.element, 'type');
|
|
266
273
|
|
|
267
274
|
if (type === 'horizontal') {
|
|
268
275
|
return {
|
|
@@ -285,7 +292,7 @@ function getTypes(splitter: OuiSplitter, width?: number, height?: number): OuiSp
|
|
|
285
292
|
}
|
|
286
293
|
|
|
287
294
|
function getValue(element: HTMLElement, name: string, defaultValue: number): number {
|
|
288
|
-
const value =
|
|
295
|
+
const value = getAttribute(element, name);
|
|
289
296
|
|
|
290
297
|
if (isNullableOrWhitespace(value)) {
|
|
291
298
|
return defaultValue;
|
|
@@ -408,11 +415,11 @@ function onObservation(entries: ResizeObserverEntry[]): void {
|
|
|
408
415
|
|
|
409
416
|
splitter.types = getTypes(splitter, entry.contentRect.width, entry.contentRect.height);
|
|
410
417
|
|
|
411
|
-
|
|
412
|
-
splitter.element
|
|
413
|
-
|
|
414
|
-
splitter.
|
|
415
|
-
|
|
418
|
+
setAttribute(
|
|
419
|
+
splitter.element,
|
|
420
|
+
ATTRIBUTE_HORIZONTAL,
|
|
421
|
+
splitter.types.horizontal ? '' : undefined,
|
|
422
|
+
);
|
|
416
423
|
|
|
417
424
|
setAriaOrientation(splitter);
|
|
418
425
|
}
|
|
@@ -436,8 +443,8 @@ function onPointerdown(event: Event): void {
|
|
|
436
443
|
event.preventDefault();
|
|
437
444
|
}
|
|
438
445
|
|
|
439
|
-
splitter.handle
|
|
440
|
-
splitter.separator
|
|
446
|
+
setAttribute(splitter.handle, ATTRIBUTE_ACTIVE, '');
|
|
447
|
+
setAttribute(splitter.separator, ATTRIBUTE_ACTIVE, '');
|
|
441
448
|
|
|
442
449
|
splitter.rectangle = splitter.element.getBoundingClientRect();
|
|
443
450
|
|
|
@@ -468,17 +475,15 @@ function onPointerup(): void {
|
|
|
468
475
|
}
|
|
469
476
|
|
|
470
477
|
function setAriaOrientation(splitter: OuiSplitter): void {
|
|
471
|
-
splitter.separator.
|
|
472
|
-
'aria-orientation',
|
|
473
|
-
splitter.types.horizontal ? 'horizontal' : 'vertical',
|
|
474
|
-
);
|
|
478
|
+
setAria(splitter.separator, 'orientation', splitter.types.horizontal ? 'horizontal' : 'vertical');
|
|
475
479
|
}
|
|
476
480
|
|
|
477
481
|
function setAriaValue(splitter: OuiSplitter): void {
|
|
478
|
-
splitter.separator
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
+
setAria(splitter.separator, {
|
|
483
|
+
valuemax: splitter.values.max,
|
|
484
|
+
valuemin: splitter.values.min,
|
|
485
|
+
valuenow: splitter.values.now.current,
|
|
486
|
+
});
|
|
482
487
|
}
|
|
483
488
|
|
|
484
489
|
function setSize(splitter: OuiSplitter, size: number, previous?: boolean): void {
|
|
@@ -488,7 +493,7 @@ function setSize(splitter: OuiSplitter, size: number, previous?: boolean): void
|
|
|
488
493
|
splitter.values.now.previous = size;
|
|
489
494
|
}
|
|
490
495
|
|
|
491
|
-
splitter.element
|
|
496
|
+
setStyle(splitter.element, '--oui-splitter-size', `${size}`);
|
|
492
497
|
|
|
493
498
|
setAriaValue(splitter);
|
|
494
499
|
}
|
|
@@ -557,5 +562,5 @@ on(document, 'keydown', onKeydown);
|
|
|
557
562
|
on(document, 'mousedown', onPointerdown);
|
|
558
563
|
on(document, 'pointermove', onPointermove);
|
|
559
564
|
on(document, 'pointerup', onPointerup);
|
|
560
|
-
on(document, 'touchcancel', onPointerup
|
|
565
|
+
on(document, 'touchcancel', onPointerup);
|
|
561
566
|
on(document, 'touchstart', onPointerdown, {passive: false});
|