@oscarpalmer/oui 0.20.0 → 0.21.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.
@@ -0,0 +1,243 @@
1
+ import { attributable } from "./internal/attributable.mjs";
2
+ import { Draggable, addDraggable, removeDraggable } from "./internal/draggable.mjs";
3
+ import { getPosition } from "@oscarpalmer/toretto/event";
4
+ import { findAncestor, getElementFromPosition } from "@oscarpalmer/toretto/find";
5
+ import { createElement } from "@oscarpalmer/toretto/create";
6
+ //#region src/js/sortable.ts
7
+ var Sortable = class extends Draggable {
8
+ connections;
9
+ origin;
10
+ get dragged() {
11
+ return getDragPlaceholder();
12
+ }
13
+ constructor(element) {
14
+ super("sort", element, {
15
+ container: {
16
+ attribute: ATTRIBUTE_CONTAINER,
17
+ map: CONTAINERS
18
+ },
19
+ direction: { attribute: ATTRIBUTE_DIRECTION },
20
+ drag: {
21
+ onBegin,
22
+ onCancel,
23
+ onEnd,
24
+ onMove
25
+ },
26
+ position: {
27
+ getOffset: getOffsetPosition,
28
+ getOriginal: getOriginalPosition
29
+ }
30
+ });
31
+ this.connections = this.element.getAttribute(ATTRIBUTE_CONNECTIONS);
32
+ }
33
+ };
34
+ function afterEnd(insert, from, events, elements, position, valid) {
35
+ elements.origin?.removeAttribute(ATTRIBUTE_ORIGIN);
36
+ from.origin = void 0;
37
+ resetPlaceholders();
38
+ if (!valid || events.some((event) => event.defaultPrevented)) return;
39
+ if (insert !== "insert") {
40
+ elements.target.append(elements.origin);
41
+ return;
42
+ }
43
+ if (position == null) return;
44
+ const { height, top } = elements.target.getBoundingClientRect();
45
+ let insertion;
46
+ if (position.y < top + height / 2) insertion = INSERT_BEFORE;
47
+ else insertion = INSERT_AFTER;
48
+ elements.target?.insertAdjacentElement(insertion, elements.origin);
49
+ document.body.removeAttribute(ATTRIBUTE_ACTIVE);
50
+ }
51
+ function getDragPlaceholder() {
52
+ placeholder.drag.default ??= createElement("div");
53
+ return placeholder.drag.custom ?? placeholder.drag.default;
54
+ }
55
+ function getOffsetPosition(_, draggable, element, position) {
56
+ const rectangle = element.getBoundingClientRect();
57
+ return {
58
+ x: draggable.horizontal ? 0 : position.x - rectangle.left,
59
+ y: draggable.vertical ? 0 : position.y - rectangle.top
60
+ };
61
+ }
62
+ function getOriginalPosition(_, __, position) {
63
+ return position;
64
+ }
65
+ function getPositionPlaceholder(sortable) {
66
+ placeholder.position ??= createElement("div");
67
+ placeholder.position.setAttribute(ATTRIBUTE_PLACEHOLDER_POSITION, "");
68
+ if (sortable.origin != null) placeholder.position.style.setProperty("--placeholder-height", `${sortable.origin.getBoundingClientRect().height}px`);
69
+ return placeholder.position;
70
+ }
71
+ function onBegin(_, state, draggable, element) {
72
+ const sortable = draggable;
73
+ sortable.origin = element;
74
+ setPlaceholder(state, sortable, element);
75
+ element.setAttribute(ATTRIBUTE_ORIGIN, "");
76
+ document.body.setAttribute(ATTRIBUTE_ACTIVE, "");
77
+ }
78
+ function onCancel(_, draggable) {
79
+ const sortable = draggable;
80
+ const { origin } = sortable;
81
+ origin?.removeAttribute(ATTRIBUTE_ORIGIN);
82
+ document.body.removeAttribute(ATTRIBUTE_ACTIVE);
83
+ resetPlaceholders();
84
+ sortable.origin = void 0;
85
+ }
86
+ function onEnd(event, state, draggable, reset) {
87
+ const from = draggable;
88
+ const { origin } = from;
89
+ const position = getPosition(event) ?? {
90
+ x: 0,
91
+ y: 0
92
+ };
93
+ const target = getElementFromPosition(position)[0];
94
+ let element = findAncestor(target, SELECTOR_ITEM);
95
+ if (element?.hasAttribute(ATTRIBUTE_PLACEHOLDER_POSITION)) if (element.getAttribute(ATTRIBUTE_PLACEHOLDER_POSITION) === "after") element = element.previousElementSibling;
96
+ else element = element.nextElementSibling;
97
+ let insert = "insert";
98
+ let to;
99
+ if (element == null) {
100
+ element = findAncestor(target, SELECTOR);
101
+ to = state.instances.get(element)?.sortable;
102
+ insert = element == null ? void 0 : element.children.length === 0 ? "append" : "prepend";
103
+ } else to = state.instances.get(element.parentElement)?.sortable;
104
+ const valid = insert != null && validatePosition(state, from, to, position);
105
+ const options = {
106
+ detail: valid ? {
107
+ from: {
108
+ element: origin,
109
+ position: { ...state.original }
110
+ },
111
+ to: {
112
+ element,
113
+ position: { ...position }
114
+ }
115
+ } : void 0,
116
+ bubbles: false,
117
+ cancelable: true
118
+ };
119
+ const fromEvent = new CustomEvent(EVENT_END, options);
120
+ const toEvent = new CustomEvent(EVENT_END, options);
121
+ from.element.dispatchEvent(fromEvent);
122
+ if (from !== to) to?.element.dispatchEvent(toEvent);
123
+ setTimeout(() => {
124
+ afterEnd(insert, from, [fromEvent, toEvent], {
125
+ origin,
126
+ target: element
127
+ }, position, valid);
128
+ reset();
129
+ });
130
+ }
131
+ function onMove(event, state, draggable, position) {
132
+ const from = draggable;
133
+ const fromElement = from.origin;
134
+ const toElement = findAncestor(event, SELECTOR_ITEM);
135
+ let to;
136
+ if (toElement == null) to = state.instances.get(findAncestor(event, SELECTOR))?.sortable;
137
+ else to = state.instances.get(toElement.parentElement)?.sortable;
138
+ if (!validatePosition(state, from, to, position.original)) return;
139
+ const placeholder = getPositionPlaceholder(from);
140
+ if (toElement != null && toElement !== fromElement) {
141
+ const { height, top } = toElement.getBoundingClientRect();
142
+ let place;
143
+ if (position.calculated.y < top + height / 2) {
144
+ place = "before";
145
+ toElement.insertAdjacentElement(INSERT_BEFORE, placeholder);
146
+ } else {
147
+ place = "after";
148
+ toElement.insertAdjacentElement(INSERT_AFTER, placeholder);
149
+ }
150
+ placeholder.setAttribute(ATTRIBUTE_PLACEHOLDER_POSITION, place);
151
+ } else placeholder.remove();
152
+ return {
153
+ from: {
154
+ element: fromElement,
155
+ position: { ...state.original }
156
+ },
157
+ to: {
158
+ element: toElement,
159
+ position: { ...position.calculated }
160
+ }
161
+ };
162
+ }
163
+ function resetPlaceholders() {
164
+ placeholder.drag.custom?.remove();
165
+ placeholder.drag.default?.remove();
166
+ placeholder.position?.remove();
167
+ placeholder.drag.custom = void 0;
168
+ }
169
+ function setPlaceholder(state, sortable, origin) {
170
+ const element = getDragPlaceholder();
171
+ placeholder.drag.default.innerHTML = "";
172
+ updatePlaceholder(sortable, element);
173
+ const event = new CustomEvent(EVENT_PLACEHOLDER, {
174
+ cancelable: true,
175
+ detail: {
176
+ origin,
177
+ parent: sortable.element,
178
+ placeholder: element,
179
+ create: (value) => {
180
+ if (!(value instanceof HTMLElement)) return;
181
+ placeholder.drag.custom = value;
182
+ updatePlaceholder(sortable, value);
183
+ state.element = {
184
+ rectangle: value.getBoundingClientRect(),
185
+ node: value
186
+ };
187
+ }
188
+ }
189
+ });
190
+ sortable.element.dispatchEvent(event);
191
+ setTimeout(() => {
192
+ if (!event.defaultPrevented && placeholder.drag.custom == null) placeholder.drag.default?.append(element.cloneNode(true));
193
+ document.body.append(placeholder.drag.custom ?? placeholder.drag.default ?? "");
194
+ });
195
+ }
196
+ function updatePlaceholder(sortable, element) {
197
+ element.style.position = "fixed";
198
+ element.setAttribute(ATTRIBUTE_PLACEHOLDER_DRAG, "");
199
+ if (sortable.horizontal) element.setAttribute(ATTRIBUTE_HORIZONTAL, "");
200
+ else element.removeAttribute(ATTRIBUTE_HORIZONTAL);
201
+ if (sortable.vertical) element.setAttribute(ATTRIBUTE_VERTICAL, "");
202
+ else element.removeAttribute(ATTRIBUTE_VERTICAL);
203
+ }
204
+ function validatePosition(_, from, to, position) {
205
+ let valid = true;
206
+ const rectangle = from.origin?.getBoundingClientRect();
207
+ if (rectangle != null && !from.horizontal) valid = position.x >= rectangle.left && position.x <= rectangle.right;
208
+ if (valid && rectangle != null && !from.vertical) valid = position.y >= rectangle.top && position.y <= rectangle.bottom;
209
+ if (valid && from.connections != null) valid = to?.element.matches(from.connections) ?? true;
210
+ if (valid && to?.connections != null) valid = from.element.matches(to.connections);
211
+ return valid;
212
+ }
213
+ const ATTRIBUTE = "oui-sortable";
214
+ const ATTRIBUTE_ACTIVE = `${ATTRIBUTE}-active`;
215
+ const ATTRIBUTE_CONNECTIONS = `${ATTRIBUTE}-connections`;
216
+ const ATTRIBUTE_CONTAINER = `${ATTRIBUTE}-container`;
217
+ const ATTRIBUTE_DIRECTION = `${ATTRIBUTE}-direction`;
218
+ const ATTRIBUTE_HORIZONTAL = `${ATTRIBUTE}-horizontal`;
219
+ const ATTRIBUTE_ORIGIN = `${ATTRIBUTE}-origin`;
220
+ const ATTRIBUTE_PLACEHOLDER_DRAG = `${ATTRIBUTE}-placeholder-drag`;
221
+ const ATTRIBUTE_PLACEHOLDER_POSITION = `${ATTRIBUTE}-placeholder-position`;
222
+ const ATTRIBUTE_VERTICAL = `${ATTRIBUTE}-vertical`;
223
+ const CONTAINERS = /* @__PURE__ */ new Map();
224
+ const EVENT_END = "sort:end";
225
+ const EVENT_PLACEHOLDER = "sort:placeholder";
226
+ const INSERT_AFTER = "afterend";
227
+ const INSERT_BEFORE = "beforebegin";
228
+ const SELECTOR = `[${ATTRIBUTE}]`;
229
+ const SELECTOR_ITEM = `[${ATTRIBUTE}] > *`;
230
+ const bound = {
231
+ constructor: Sortable,
232
+ property: "sortable"
233
+ };
234
+ const placeholder = {
235
+ drag: {
236
+ custom: void 0,
237
+ default: void 0
238
+ },
239
+ position: void 0
240
+ };
241
+ attributable(ATTRIBUTE, addDraggable.bind(bound), removeDraggable.bind(bound));
242
+ //#endregion
243
+ export { Sortable };
@@ -1,10 +1,10 @@
1
+ import { clamp } from "@oscarpalmer/atoms/number";
1
2
  import { getPosition, on } from "@oscarpalmer/toretto/event";
2
3
  import { findAncestor } from "@oscarpalmer/toretto/find";
3
- import { clamp } from "@oscarpalmer/atoms/number";
4
- import { isNullableOrWhitespace } from "@oscarpalmer/atoms/is";
5
- import { round } from "@oscarpalmer/atoms/math";
6
4
  import { toggleStyles } from "@oscarpalmer/toretto/style";
7
5
  import supportsTouch from "@oscarpalmer/toretto/touch";
6
+ import { isNullableOrWhitespace } from "@oscarpalmer/atoms/is";
7
+ import { round } from "@oscarpalmer/atoms/math";
8
8
  //#region src/js/splitter.ts
9
9
  var OuiSplitterElement = class extends HTMLElement {
10
10
  static observedAttributes = [
@@ -216,7 +216,7 @@ function onObservation(entries) {
216
216
  });
217
217
  }
218
218
  function onPointerdown(event) {
219
- if (event.type === "mousedown" && supportsTouch.value || event.type === "touchstart" && !supportsTouch.value) return;
219
+ if (supportsTouch.value ? event.type === "mousedown" : event.type === "touchstart") return;
220
220
  splitter = MAPPED_ELEMENTS.get(findAncestor(event, SELECTOR_HANDLE));
221
221
  if (splitter == null) return;
222
222
  if (supportsTouch.value) event.preventDefault();
@@ -1,4 +1,4 @@
1
1
  //#region src/js/tooltip.d.ts
2
- declare function setDelay(value: number): void;
2
+ declare function setTooltipDelay(value: number): void;
3
3
  //#endregion
4
- export { setDelay };
4
+ export { setTooltipDelay };
@@ -1,5 +1,5 @@
1
- import { attributable } from "./attributable.mjs";
2
- import { Floatable } from "./floatable.mjs";
1
+ import { attributable } from "./internal/attributable.mjs";
2
+ import { Floatable } from "./internal/floatable.mjs";
3
3
  import { on } from "@oscarpalmer/toretto/event";
4
4
  import { findAncestor } from "@oscarpalmer/toretto/find";
5
5
  import { isNullableOrWhitespace } from "@oscarpalmer/atoms/is";
@@ -151,7 +151,7 @@ function reset(anchor, attributes) {
151
151
  if (attributes.ariaDescribedby == null) anchor.removeAttribute("aria-describedby");
152
152
  else anchor.setAttribute("aria-describedby", attributes.ariaDescribedby);
153
153
  }
154
- function setDelay(value) {
154
+ function setTooltipDelay(value) {
155
155
  delay = typeof value === "number" && value >= 0 ? value : delay;
156
156
  }
157
157
  const SELECTOR = "oui-tooltip";
@@ -173,4 +173,4 @@ on(document, "focusout", onDeactivate);
173
173
  on(document, "mousemove", onActivate);
174
174
  on(document, "touchstart", onActivate);
175
175
  //#endregion
176
- export { setDelay };
176
+ export { setTooltipDelay };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oscarpalmer/oui",
3
- "version": "0.20.0",
3
+ "version": "0.21.0",
4
4
  "description": "Styles, components, and utilities for building websites and applications.",
5
5
  "keywords": [
6
6
  "css",
@@ -51,32 +51,34 @@
51
51
  }
52
52
  },
53
53
  "scripts": {
54
- "build": "vp run build:js && vp run build:css",
55
- "build:css": "vp run build:postcss && vpx sass ./postcss:./dist/css --no-source-map",
56
- "build:postcss": "vpx postcss ./src/css/*.scss -d ./postcss -u autoprefixer postcss-input-range --parser postcss-scss",
57
- "build:js": "vpx vp pack",
54
+ "build": "vp run clean && vp pack && vp run css:build",
55
+ "clean": "rm -rf ./dist && rm -rf ./postcss",
56
+ "css:build": "vp run postcss:build && vpx sass ./postcss:./dist/css --no-source-map",
57
+ "css:watch": "vpx sass ./postcss:./dist/css --no-source-map --watch",
58
+ "postcss:build": "vpx postcss ./src/css/*.scss -d ./postcss -u autoprefixer postcss-input-range --parser postcss-scss",
59
+ "postcss:watch": "vpx postcss ./src/css/*.scss -d ./postcss -u autoprefixer postcss-input-range --parser postcss-scss --watch",
58
60
  "test": "vpx vitest --coverage",
59
- "tsdown:build": "vpx tsdown -c ./tsdown.config.ts",
60
- "tsdown:watch": "vpx tsdown -c ./tsdown.config.ts --watch",
61
- "watch:css": "vpx sass ./postcss:./dist/css --no-source-map --watch",
62
- "watch:postcss": "vpx postcss ./src/css/*.scss -d ./postcss -u autoprefixer postcss-input-range --parser postcss-scss --watch",
63
- "watch:js": "vpx vite build --watch"
61
+ "tsdown": "vpx tsdown -c ./tsdown.config.ts --watch",
62
+ "watch": "vp run clean && concurrently \"php -S localhost:3000\" \"vp run postcss:watch\" \"vp run css:watch\" \"vp run tsdown\""
64
63
  },
65
64
  "dependencies": {
66
- "@oscarpalmer/atoms": "^0.170",
67
- "@oscarpalmer/toretto": "^0.41"
65
+ "@oscarpalmer/atoms": "^0.187.2",
66
+ "@oscarpalmer/toretto": "^0.46"
68
67
  },
69
68
  "devDependencies": {
69
+ "@oxlint/plugins": "^1.67",
70
70
  "@vitest/coverage-istanbul": "^4.1",
71
- "autoprefixer": "^10.4",
72
- "jsdom": "^29",
71
+ "autoprefixer": "^10.5",
72
+ "concurrently": "^9.2",
73
+ "jsdom": "^29.1",
73
74
  "postcss-cli": "^11",
74
75
  "postcss-input-range": "^6",
75
76
  "postcss-scss": "^4",
76
- "sass": "^1.98",
77
- "stylelint": "^17.6",
77
+ "sass": "^1.100",
78
+ "stylelint": "^17.11",
78
79
  "stylelint-config-standard-scss": "^17",
79
- "typescript": "^5.9",
80
+ "tsdown": "^0.22",
81
+ "typescript": "^6",
80
82
  "vite": "npm:@voidzero-dev/vite-plus-core@latest",
81
83
  "vite-plus": "latest",
82
84
  "vitest": "npm:@voidzero-dev/vite-plus-test@latest"
@@ -1,4 +1,4 @@
1
- import {attributable} from '../attributable';
1
+ import {attributable} from '../internal/attributable';
2
2
  import {createFocusTrap, FOCUS_TRAP_SELECTOR, FOCUSTRAPS_ALL} from './embedded';
3
3
 
4
4
  function removeFocusTraps(element: HTMLElement): void {
package/src/js/index.ts CHANGED
@@ -1,4 +1,6 @@
1
- export * from './focus-trap/standalone';
1
+ import './focus-trap/standalone';
2
+ import './movable';
2
3
  export * from './popover';
4
+ import './sortable';
3
5
  export * from './splitter';
4
6
  export * from './tooltip';