@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.
Files changed (73) hide show
  1. package/dist/css/floatable.css +9 -2
  2. package/dist/css/message.css +1 -0
  3. package/dist/css/reset.css +1 -0
  4. package/dist/js/dialog.d.mts +4 -0
  5. package/dist/js/dialog.mjs +33 -1
  6. package/dist/js/draggable-zszaW1qS.d.mts +155 -0
  7. package/dist/js/floatable/floatable.embedded.d.mts +2 -0
  8. package/dist/js/floatable/{embedded.mjs → floatable.embedded.mjs} +16 -11
  9. package/dist/js/floatable/floatable.standalone.d.mts +2 -0
  10. package/dist/js/floatable/floatable.standalone.mjs +2 -0
  11. package/dist/js/{embedded-CSOh5njI.d.mts → floatable.embedded-YxGwzgbE.d.mts} +5 -2
  12. package/dist/js/focus-trap/focus-trap.embedded.d.mts +2 -0
  13. package/dist/js/focus-trap/{embedded.mjs → focus-trap.embedded.mjs} +17 -15
  14. package/dist/js/focus-trap/focus-trap.standalone.d.mts +2 -0
  15. package/dist/js/focus-trap/focus-trap.standalone.mjs +2 -0
  16. package/dist/js/{embedded-CHK82Uh5.d.mts → focus-trap.embedded-DR8NpZfr.d.mts} +18 -18
  17. package/dist/js/index.d.mts +5 -3
  18. package/dist/js/index.mjs +11 -7
  19. package/dist/js/internal/draggable.d.mts +2 -2
  20. package/dist/js/internal/draggable.mjs +147 -95
  21. package/dist/js/movable/movable.embedded.d.mts +2 -0
  22. package/dist/js/movable/movable.embedded.mjs +184 -0
  23. package/dist/js/movable/movable.standalone.d.mts +2 -0
  24. package/dist/js/movable/movable.standalone.mjs +2 -0
  25. package/dist/js/popover.d.mts +2 -2
  26. package/dist/js/popover.mjs +4 -3
  27. package/dist/js/resizable/resizable.embedded.d.mts +2 -0
  28. package/dist/js/resizable/resizable.embedded.mjs +186 -0
  29. package/dist/js/resizable/resizable.standalone.d.mts +2 -0
  30. package/dist/js/resizable/resizable.standalone.mjs +2 -0
  31. package/dist/js/resizable.embedded-CXrUVPzR.d.mts +35 -0
  32. package/dist/js/sortable/sortable.embedded.d.mts +2 -0
  33. package/dist/js/{sortable.mjs → sortable/sortable.embedded.mjs} +88 -63
  34. package/dist/js/sortable/sortable.standalone.d.mts +2 -0
  35. package/dist/js/sortable/sortable.standalone.mjs +2 -0
  36. package/dist/js/splitter.mjs +6 -6
  37. package/dist/js/toolbar.mjs +6 -6
  38. package/dist/js/tooltip.d.mts +2 -2
  39. package/dist/js/tooltip.mjs +36 -33
  40. package/package.json +15 -11
  41. package/src/css/floatable.scss +9 -2
  42. package/src/css/form.scss +6 -6
  43. package/src/css/message.scss +1 -0
  44. package/src/css/reset.scss +1 -0
  45. package/src/js/dialog.ts +61 -1
  46. package/src/js/floatable/{embedded.ts → floatable.embedded.ts} +18 -10
  47. package/src/js/floatable/floatable.standalone.ts +6 -0
  48. package/src/js/focus-trap/{embedded.ts → focus-trap.embedded.ts} +20 -17
  49. package/src/js/focus-trap/focus-trap.standalone.ts +1 -0
  50. package/src/js/index.ts +5 -4
  51. package/src/js/internal/draggable.ts +310 -169
  52. package/src/js/movable/movable.embedded.ts +328 -0
  53. package/src/js/movable/movable.standalone.ts +2 -0
  54. package/src/js/popover.ts +3 -2
  55. package/src/js/resizable/resizable.embedded.ts +383 -0
  56. package/src/js/resizable/resizable.standalone.ts +5 -0
  57. package/src/js/{sortable.ts → sortable/sortable.embedded.ts} +170 -103
  58. package/src/js/sortable/sortable.standalone.ts +2 -0
  59. package/src/js/splitter.ts +1 -1
  60. package/src/js/tooltip.ts +49 -43
  61. package/dist/js/floatable/embedded.d.mts +0 -2
  62. package/dist/js/floatable/standalone.d.mts +0 -2
  63. package/dist/js/floatable/standalone.mjs +0 -2
  64. package/dist/js/focus-trap/embedded.d.mts +0 -2
  65. package/dist/js/focus-trap/standalone.d.mts +0 -2
  66. package/dist/js/focus-trap/standalone.mjs +0 -6
  67. package/dist/js/movable-dNqBctx3.d.mts +0 -101
  68. package/dist/js/movable.d.mts +0 -2
  69. package/dist/js/movable.mjs +0 -152
  70. package/dist/js/sortable.d.mts +0 -2
  71. package/src/js/floatable/standalone.ts +0 -1
  72. package/src/js/focus-trap/standalone.ts +0 -6
  73. 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
@@ -0,0 +1,5 @@
1
+ export {
2
+ createResizable as createOuiResizable,
3
+ type CreateOuiResizableOptions,
4
+ type OuiResizable,
5
+ } from './resizable.embedded';