@xleddyl/443-components-vue 0.10.2 → 0.11.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 (53) hide show
  1. package/CATALOG.md +185 -175
  2. package/README.md +47 -21
  3. package/dist/components/data/Tag.vue +2 -0
  4. package/dist/components/data/Tag.vue.d.ts +2 -0
  5. package/dist/components/data/TagList.vue +239 -0
  6. package/dist/components/data/TagList.vue.d.ts +53 -0
  7. package/dist/components/data/index.d.ts +1 -0
  8. package/dist/components/data/index.mjs +1 -0
  9. package/dist/components/data/types.d.ts +15 -0
  10. package/dist/components/forms/Combobox.vue +33 -4
  11. package/dist/components/forms/Combobox.vue.d.ts +3 -1
  12. package/dist/components/forms/NumberInput.vue.d.ts +1 -1
  13. package/dist/components/forms/PinInput.vue.d.ts +2 -2
  14. package/dist/components/forms/types.d.ts +2 -0
  15. package/dist/components/layout/AppShell.vue +14 -2
  16. package/dist/components/layout/AppShell.vue.d.ts +3 -1
  17. package/dist/components/layout/Stepper.vue +148 -0
  18. package/dist/components/layout/Stepper.vue.d.ts +420 -0
  19. package/dist/components/layout/index.d.ts +1 -0
  20. package/dist/components/layout/index.mjs +1 -0
  21. package/dist/components/layout/types.d.ts +12 -0
  22. package/dist/components/overlays/Toaster.vue +10 -0
  23. package/dist/components/overlays/Toaster.vue.d.ts +1 -1
  24. package/dist/components/templates/DashboardLayout.vue +2 -0
  25. package/dist/components/templates/DashboardLayout.vue.d.ts +3 -1
  26. package/dist/components/templates/types.d.ts +2 -1
  27. package/dist/composables/data.d.ts +1 -0
  28. package/dist/composables/data.mjs +1 -0
  29. package/dist/composables/useSortable.d.ts +38 -0
  30. package/dist/composables/useSortable.mjs +215 -0
  31. package/dist/grid/cells/BadgeCell.vue +90 -18
  32. package/dist/grid/cells/BadgeCell.vue.d.ts +14 -14
  33. package/dist/grid/data-grid.css +24 -0
  34. package/dist/grid/spreadsheet.mjs +8 -4
  35. package/dist/grid/types.d.ts +2 -0
  36. package/dist/internal/data/sortable.d.ts +5 -0
  37. package/dist/internal/data/sortable.mjs +23 -0
  38. package/dist/internal/overlays/avoid.d.ts +26 -0
  39. package/dist/internal/overlays/avoid.mjs +134 -0
  40. package/dist/locale/de.mjs +20 -0
  41. package/dist/locale/en.mjs +20 -0
  42. package/dist/locale/it.mjs +20 -0
  43. package/dist/locale/types.d.ts +20 -0
  44. package/dist/nuxt/module.mjs +14 -0
  45. package/dist/nuxt/runtime/layouts/dashboard.vue +8 -1
  46. package/dist/styles/data/tag-list.css +93 -0
  47. package/dist/styles/data.css +1 -0
  48. package/dist/styles/forced-colors.css +10 -0
  49. package/dist/styles/layout/app-shell.css +23 -0
  50. package/dist/styles/layout/stepper.css +244 -0
  51. package/dist/styles/layout.css +1 -0
  52. package/dist/styles/overlays/toast.css +17 -12
  53. package/package.json +1 -1
@@ -0,0 +1,215 @@
1
+ import { nextTick, onScopeDispose, readonly, ref, toValue } from "vue";
2
+ import { useLocale } from "./useLocale.mjs";
3
+ import { useLiveMessage } from "../internal/core/live.mjs";
4
+ import { isClient } from "../internal/core/env.mjs";
5
+ import { SORTABLE_ATTR, insertionIndex, isRtl, sortableElements } from "../internal/data/sortable.mjs";
6
+ const THRESHOLD = 4;
7
+ const INTERACTIVE = "button, a[href], input, textarea, select, [contenteditable]";
8
+ export function useSortable(container, options) {
9
+ const { t } = useLocale();
10
+ const live = useLiveMessage();
11
+ const dragging = ref(null);
12
+ let drag = null;
13
+ const axis = () => toValue(options.orientation) ?? "vertical";
14
+ const isDisabled = () => Boolean(toValue(options.disabled));
15
+ const list = () => toValue(options.items);
16
+ function movable(index) {
17
+ const item = list()[index];
18
+ if (item === void 0 || isDisabled()) return false;
19
+ return options.canMove ? options.canMove(item, index) : true;
20
+ }
21
+ function labelOf(item) {
22
+ return options.label ? options.label(item) : String(item);
23
+ }
24
+ function announceMove(move2) {
25
+ live.announce(
26
+ options.movedText ? options.movedText(move2) : t("sortable.moved", {
27
+ label: labelOf(move2.item),
28
+ position: move2.to + 1,
29
+ count: move2.items.length
30
+ })
31
+ );
32
+ }
33
+ function move(from, to, moveOptions = {}) {
34
+ const items = list();
35
+ const target = Math.max(0, Math.min(items.length - 1, to));
36
+ if (from === target || !movable(from)) return false;
37
+ const next = [...items];
38
+ const [item] = next.splice(from, 1);
39
+ next.splice(target, 0, item);
40
+ const entry = {
41
+ item,
42
+ from,
43
+ to: target,
44
+ items: next,
45
+ source: moveOptions.source ?? "api"
46
+ };
47
+ const focused = isClient() ? document.activeElement : null;
48
+ options.onMove(next, entry);
49
+ if (moveOptions.announce) announceMove(entry);
50
+ if (focused && focused !== document.body)
51
+ nextTick(() => {
52
+ if (focused.isConnected && document.activeElement !== focused)
53
+ focused.focus({ preventScroll: true });
54
+ });
55
+ return true;
56
+ }
57
+ function keyDelta(event) {
58
+ const rtl = isRtl(event.currentTarget);
59
+ switch (event.key) {
60
+ case "ArrowUp":
61
+ return -1;
62
+ case "ArrowDown":
63
+ return 1;
64
+ case "ArrowLeft":
65
+ return rtl ? 1 : -1;
66
+ case "ArrowRight":
67
+ return rtl ? -1 : 1;
68
+ case "Home":
69
+ return -Infinity;
70
+ case "End":
71
+ return Infinity;
72
+ default:
73
+ return 0;
74
+ }
75
+ }
76
+ function onKeydown(event, index) {
77
+ if (!event.altKey || event.ctrlKey || event.metaKey) return;
78
+ const delta = keyDelta(event);
79
+ if (!delta) return;
80
+ event.preventDefault();
81
+ const to = delta === -Infinity ? 0 : delta === Infinity ? list().length - 1 : index + delta;
82
+ move(index, to, { announce: true, source: "keyboard" });
83
+ }
84
+ function preventSelection(event) {
85
+ event.preventDefault();
86
+ }
87
+ function suppressClick(event) {
88
+ event.preventDefault();
89
+ event.stopPropagation();
90
+ }
91
+ function listen(enable) {
92
+ if (enable) {
93
+ window.addEventListener("pointermove", onPointerMove);
94
+ window.addEventListener("pointerup", onPointerUp);
95
+ window.addEventListener("pointercancel", onPointerCancel);
96
+ window.addEventListener("keydown", onDragKeydown, true);
97
+ document.addEventListener("selectstart", preventSelection);
98
+ return;
99
+ }
100
+ window.removeEventListener("pointermove", onPointerMove);
101
+ window.removeEventListener("pointerup", onPointerUp);
102
+ window.removeEventListener("pointercancel", onPointerCancel);
103
+ window.removeEventListener("keydown", onDragKeydown, true);
104
+ document.removeEventListener("selectstart", preventSelection);
105
+ }
106
+ function follow(event) {
107
+ if (!drag) return;
108
+ const element = drag.element;
109
+ const current = element.style.translate.split(" ").map((value) => parseFloat(value) || 0);
110
+ const rect = element.getBoundingClientRect();
111
+ const layoutX = rect.left - (current[0] ?? 0);
112
+ const layoutY = rect.top - (current[1] ?? 0);
113
+ const x = event.clientX - drag.startX - (layoutX - drag.layoutX);
114
+ const y = event.clientY - drag.startY - (layoutY - drag.layoutY);
115
+ element.style.translate = `${x}px ${y}px`;
116
+ }
117
+ function onPointerDown(event, index) {
118
+ if (event.button !== 0 || !event.isPrimary || drag) return;
119
+ const element = event.currentTarget;
120
+ const target = event.target;
121
+ if (options.handle) {
122
+ if (!target.closest(options.handle)) return;
123
+ } else if (target !== element && target.closest(INTERACTIVE)) return;
124
+ if (!movable(index)) return;
125
+ const rect = element.getBoundingClientRect();
126
+ drag = {
127
+ pointerId: event.pointerId,
128
+ element,
129
+ origin: index,
130
+ index,
131
+ startX: event.clientX,
132
+ startY: event.clientY,
133
+ layoutX: rect.left,
134
+ layoutY: rect.top,
135
+ active: false
136
+ };
137
+ listen(true);
138
+ }
139
+ function onPointerMove(event) {
140
+ if (!drag || event.pointerId !== drag.pointerId) return;
141
+ if (!drag.active) {
142
+ if (Math.hypot(event.clientX - drag.startX, event.clientY - drag.startY) < THRESHOLD)
143
+ return;
144
+ drag.active = true;
145
+ dragging.value = drag.index;
146
+ window.getSelection()?.removeAllRanges();
147
+ }
148
+ event.preventDefault();
149
+ const root = toValue(container);
150
+ const others = sortableElements(root).filter((element) => element !== drag.element);
151
+ const target = insertionIndex(others, event.clientX, event.clientY, axis(), isRtl(root));
152
+ if (target !== drag.index && move(drag.index, target, { source: "pointer" })) {
153
+ drag.index = target;
154
+ dragging.value = target;
155
+ nextTick(() => follow(event));
156
+ }
157
+ follow(event);
158
+ }
159
+ function finish(cancelled) {
160
+ const state = drag;
161
+ drag = null;
162
+ listen(false);
163
+ if (!state) return;
164
+ state.element.style.translate = "";
165
+ dragging.value = null;
166
+ if (!state.active) return;
167
+ window.addEventListener("click", suppressClick, { capture: true, once: true });
168
+ setTimeout(() => window.removeEventListener("click", suppressClick, { capture: true }), 0);
169
+ if (cancelled) {
170
+ if (state.index !== state.origin) move(state.index, state.origin, { source: "pointer" });
171
+ return;
172
+ }
173
+ if (state.index !== state.origin) {
174
+ const items = list();
175
+ const item = items[state.index];
176
+ announceMove({
177
+ item,
178
+ from: state.origin,
179
+ to: state.index,
180
+ items: [...items],
181
+ source: "pointer"
182
+ });
183
+ }
184
+ }
185
+ function onPointerUp(event) {
186
+ if (drag && event.pointerId === drag.pointerId) finish(false);
187
+ }
188
+ function onPointerCancel(event) {
189
+ if (drag && event.pointerId === drag.pointerId) finish(true);
190
+ }
191
+ function onDragKeydown(event) {
192
+ if (event.key !== "Escape" || !drag?.active) return;
193
+ event.preventDefault();
194
+ event.stopPropagation();
195
+ finish(true);
196
+ }
197
+ function itemProps(index) {
198
+ return {
199
+ [SORTABLE_ATTR]: index,
200
+ "aria-keyshortcuts": movable(index) ? "Alt+ArrowLeft Alt+ArrowRight Alt+ArrowUp Alt+ArrowDown Alt+Home Alt+End" : void 0,
201
+ onPointerdown: (event) => onPointerDown(event, index),
202
+ onKeydown: (event) => onKeydown(event, index)
203
+ };
204
+ }
205
+ onScopeDispose(() => {
206
+ if (drag) finish(true);
207
+ });
208
+ return {
209
+ dragging: readonly(dragging),
210
+ announcement: live.message,
211
+ itemProps,
212
+ move,
213
+ canMove: movable
214
+ };
215
+ }
@@ -1,11 +1,60 @@
1
1
  <template>
2
- <template v-if="text">
3
- <Badge v-if="params.variant" :class="badgeClass" :tone :variant="params.variant" :size>{{
4
- text
5
- }}</Badge>
6
- <span v-else class="k-data-grid-badge" :class="[`k-data-grid-badge-${tone}`, badgeClass]">{{
7
- text
8
- }}</span>
2
+ <span
3
+ v-if="isList && entries.length"
4
+ class="k-data-grid-badges"
5
+ :class="{ 'is-wrap': params.wrap }"
6
+ :title="fullText"
7
+ >
8
+ <template v-for="entry in shown" :key="entry.key">
9
+ <Badge
10
+ v-if="params.variant"
11
+ :class="entry.badgeClass"
12
+ :tone="entry.tone"
13
+ :variant="params.variant"
14
+ :size
15
+ >{{ entry.text }}</Badge
16
+ >
17
+ <span
18
+ v-else
19
+ class="k-data-grid-badge"
20
+ :class="[`k-data-grid-badge-${entry.tone}`, entry.badgeClass]"
21
+ >{{ entry.text }}</span
22
+ >
23
+ </template>
24
+ <template v-if="hiddenCount > 0">
25
+ <Badge
26
+ v-if="params.variant"
27
+ class="k-data-grid-badge-more"
28
+ tone="neutral"
29
+ :variant="params.variant"
30
+ :size
31
+ aria-hidden="true"
32
+ >+{{ hiddenCount }}</Badge
33
+ >
34
+ <span
35
+ v-else
36
+ class="k-data-grid-badge k-data-grid-badge-neutral k-data-grid-badge-more"
37
+ aria-hidden="true"
38
+ >+{{ hiddenCount }}</span
39
+ >
40
+ <span class="k-visually-hidden">{{ moreText }}</span>
41
+ </template>
42
+ </span>
43
+ <template v-else-if="!isList && single">
44
+ <Badge
45
+ v-if="params.variant"
46
+ :class="single.badgeClass"
47
+ :tone="single.tone"
48
+ :variant="params.variant"
49
+ :size
50
+ >{{ single.text }}</Badge
51
+ >
52
+ <span
53
+ v-else
54
+ class="k-data-grid-badge"
55
+ :class="[`k-data-grid-badge-${single.tone}`, single.badgeClass]"
56
+ >{{ single.text }}</span
57
+ >
9
58
  </template>
10
59
  <EmptyCell v-else :context="cellContext" />
11
60
  </template>
@@ -13,22 +62,45 @@
13
62
  <script setup>
14
63
  import { computed } from "vue";
15
64
  import Badge from "../../components/data/Badge.vue";
65
+ import { formatLocaleMessage } from "../../locale";
16
66
  import EmptyCell from "./EmptyCell.vue";
17
67
  const props = defineProps({
18
68
  params: { type: Object, required: true }
19
69
  });
20
- const key = computed(() => {
21
- const raw = props.params.value;
22
- return raw === null || raw === void 0 ? "" : String(raw);
23
- });
24
- const text = computed(() => key.value ? props.params.labels?.[key.value] ?? key.value : "");
25
- const tone = computed(
26
- () => props.params.tones?.[key.value] ?? props.params.defaultTone ?? "neutral"
27
- );
70
+ const cellContext = computed(() => props.params.context);
28
71
  const size = computed(() => props.params.size ?? "sm");
29
- const badgeClass = computed(() => {
72
+ const isList = computed(() => Array.isArray(props.params.value));
73
+ function keyOf(raw) {
74
+ return raw === null || raw === void 0 ? "" : String(raw);
75
+ }
76
+ function entryFor(raw, index) {
77
+ const key = keyOf(raw);
78
+ if (!key) return null;
30
79
  const source = props.params.badgeClass;
31
- return typeof source === "function" ? source(props.params.value, props.params.data) : source;
80
+ return {
81
+ key: `${index}-${key}`,
82
+ text: props.params.labels?.[key] ?? key,
83
+ tone: props.params.tones?.[key] ?? props.params.defaultTone ?? "neutral",
84
+ badgeClass: typeof source === "function" ? source(raw, props.params.data) : source
85
+ };
86
+ }
87
+ const single = computed(() => isList.value ? null : entryFor(props.params.value, 0));
88
+ const entries = computed(() => {
89
+ if (!isList.value) return [];
90
+ return props.params.value.map((raw, index) => entryFor(raw, index)).filter((entry) => entry !== null);
32
91
  });
33
- const cellContext = computed(() => props.params.context);
92
+ const limit = computed(() => {
93
+ const max = props.params.max;
94
+ return max === void 0 || max < 1 ? entries.value.length : Math.floor(max);
95
+ });
96
+ const shown = computed(() => entries.value.slice(0, limit.value));
97
+ const hiddenCount = computed(() => entries.value.length - shown.value.length);
98
+ const fullText = computed(() => entries.value.map((entry) => entry.text).join(", "));
99
+ const moreText = computed(
100
+ () => formatLocaleMessage(
101
+ cellContext.value?.locale ?? "en",
102
+ cellContext.value?.messages?.moreValues ?? "{count} more",
103
+ { count: hiddenCount.value }
104
+ )
105
+ );
34
106
  </script>
@@ -1,13 +1,6 @@
1
1
  import type { BadgeCellParams, GridCellContext } from '../types';
2
2
  declare const __VLS_export: import("vue").DefineComponent<{
3
3
  params: import("ag-grid-community").ICellRendererParams<any, any, any> & BadgeCellParams<unknown>;
4
- label?: string | number;
5
- tone?: import("../../index.ts").Tone;
6
- variant?: import("../../index.ts").BadgeVariant;
7
- size?: import("../../index.ts").BadgeSize;
8
- dot?: boolean;
9
- pulse?: boolean;
10
- icon?: import("../../index.ts").IconName;
11
4
  innerHTML?: string | undefined | undefined;
12
5
  class?: import("vue").ClassValue;
13
6
  style?: import("vue").StyleValue;
@@ -200,6 +193,13 @@ declare const __VLS_export: import("vue").DefineComponent<{
200
193
  ref?: import("vue").VNodeRef | undefined;
201
194
  ref_for?: boolean | undefined | undefined;
202
195
  ref_key?: string | undefined | undefined;
196
+ label?: string | number;
197
+ tone?: import("../../index.ts").Tone;
198
+ variant?: import("../../index.ts").BadgeVariant;
199
+ size?: import("../../index.ts").BadgeSize;
200
+ dot?: boolean;
201
+ pulse?: boolean;
202
+ icon?: import("../../index.ts").IconName;
203
203
  onVnodeBeforeMount?: (((vnode: import("vue").VNode) => void) | ((vnode: import("vue").VNode) => void)[]) | undefined;
204
204
  onVnodeMounted?: (((vnode: import("vue").VNode) => void) | ((vnode: import("vue").VNode) => void)[]) | undefined;
205
205
  onVnodeBeforeUpdate?: (((vnode: import("vue").VNode, oldVNode: import("vue").VNode) => void) | ((vnode: import("vue").VNode, oldVNode: import("vue").VNode) => void)[]) | undefined;
@@ -209,13 +209,6 @@ declare const __VLS_export: import("vue").DefineComponent<{
209
209
  context?: GridCellContext;
210
210
  }, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{
211
211
  params: import("ag-grid-community").ICellRendererParams<any, any, any> & BadgeCellParams<unknown>;
212
- label?: string | number;
213
- tone?: import("../../index.ts").Tone;
214
- variant?: import("../../index.ts").BadgeVariant;
215
- size?: import("../../index.ts").BadgeSize;
216
- dot?: boolean;
217
- pulse?: boolean;
218
- icon?: import("../../index.ts").IconName;
219
212
  innerHTML?: string | undefined | undefined;
220
213
  class?: import("vue").ClassValue;
221
214
  style?: import("vue").StyleValue;
@@ -408,6 +401,13 @@ declare const __VLS_export: import("vue").DefineComponent<{
408
401
  ref?: import("vue").VNodeRef | undefined;
409
402
  ref_for?: boolean | undefined | undefined;
410
403
  ref_key?: string | undefined | undefined;
404
+ label?: string | number;
405
+ tone?: import("../../index.ts").Tone;
406
+ variant?: import("../../index.ts").BadgeVariant;
407
+ size?: import("../../index.ts").BadgeSize;
408
+ dot?: boolean;
409
+ pulse?: boolean;
410
+ icon?: import("../../index.ts").IconName;
411
411
  onVnodeBeforeMount?: (((vnode: import("vue").VNode) => void) | ((vnode: import("vue").VNode) => void)[]) | undefined;
412
412
  onVnodeMounted?: (((vnode: import("vue").VNode) => void) | ((vnode: import("vue").VNode) => void)[]) | undefined;
413
413
  onVnodeBeforeUpdate?: (((vnode: import("vue").VNode, oldVNode: import("vue").VNode) => void) | ((vnode: import("vue").VNode, oldVNode: import("vue").VNode) => void)[]) | undefined;
@@ -163,6 +163,30 @@
163
163
  white-space: nowrap;
164
164
  }
165
165
 
166
+ .k-data-grid-badges {
167
+ display: flex;
168
+ flex-wrap: nowrap;
169
+ align-items: center;
170
+ gap: 0.25rem;
171
+ min-width: 0;
172
+ max-width: 100%;
173
+ overflow: hidden;
174
+ }
175
+
176
+ .k-data-grid-badges.is-wrap {
177
+ flex-wrap: wrap;
178
+ padding-block: 0.25rem;
179
+ line-height: 1.2;
180
+ }
181
+
182
+ .k-data-grid-badges > * {
183
+ flex-shrink: 0;
184
+ }
185
+
186
+ .k-data-grid-badges > .k-data-grid-badge-more {
187
+ font-variant-numeric: tabular-nums;
188
+ }
189
+
166
190
  .k-data-grid-badge-accent {
167
191
  --k-badge-tone: var(--k-accent-text);
168
192
  }
@@ -102,15 +102,19 @@ export function createGridSpreadsheet(options) {
102
102
  const inside = cell.row >= rect.top && cell.row <= rect.bottom && cell.col >= rect.left && cell.col <= rect.right;
103
103
  return inside ? cell : null;
104
104
  }
105
+ function onRangeEdge(params, edge) {
106
+ const cell = rangeCell(params);
107
+ return !!cell && !!rect && edge(cell, rect);
108
+ }
105
109
  const classRules = {
106
110
  "is-range": (params) => {
107
111
  const cell = rangeCell(params);
108
112
  return !!cell && !sameCell(cell, active);
109
113
  },
110
- "is-range-top": (params) => rangeCell(params)?.row === rect?.top,
111
- "is-range-bottom": (params) => rangeCell(params)?.row === rect?.bottom,
112
- "is-range-left": (params) => rangeCell(params)?.col === rect?.left,
113
- "is-range-right": (params) => rangeCell(params)?.col === rect?.right,
114
+ "is-range-top": (params) => onRangeEdge(params, (cell, area) => cell.row === area.top),
115
+ "is-range-bottom": (params) => onRangeEdge(params, (cell, area) => cell.row === area.bottom),
116
+ "is-range-left": (params) => onRangeEdge(params, (cell, area) => cell.col === area.left),
117
+ "is-range-right": (params) => onRangeEdge(params, (cell, area) => cell.col === area.right),
114
118
  "is-active": (params) => !!active && sameCell(cellOf(params), active),
115
119
  "is-fill-corner": (params) => {
116
120
  if (!rect) return false;
@@ -49,6 +49,8 @@ export interface BadgeCellParams<T = unknown> {
49
49
  variant?: BadgeVariant;
50
50
  size?: BadgeSize;
51
51
  badgeClass?: string | ((value: unknown, data: T | undefined) => string | undefined);
52
+ max?: number;
53
+ wrap?: boolean;
52
54
  }
53
55
  export type ImageCellFit = 'cover' | 'contain';
54
56
  export interface ImageCellParams<T = unknown> {
@@ -0,0 +1,5 @@
1
+ export type SortableAxis = 'horizontal' | 'vertical';
2
+ export declare const SORTABLE_ATTR = "data-k-sortable";
3
+ export declare function sortableElements(container: HTMLElement | null | undefined): HTMLElement[];
4
+ export declare function isRtl(element: Element | null | undefined): boolean;
5
+ export declare function insertionIndex(elements: HTMLElement[], x: number, y: number, axis: SortableAxis, rtl: boolean): number;
@@ -0,0 +1,23 @@
1
+ export const SORTABLE_ATTR = "data-k-sortable";
2
+ export function sortableElements(container) {
3
+ if (!container) return [];
4
+ return Array.from(container.querySelectorAll(`[${SORTABLE_ATTR}]`)).sort(
5
+ (a, b) => Number(a.getAttribute(SORTABLE_ATTR)) - Number(b.getAttribute(SORTABLE_ATTR))
6
+ );
7
+ }
8
+ export function isRtl(element) {
9
+ return element ? getComputedStyle(element).direction === "rtl" : false;
10
+ }
11
+ function isBefore(rect, x, y, axis, rtl) {
12
+ if (axis === "vertical") return rect.top + rect.height / 2 < y;
13
+ if (rect.bottom <= y) return true;
14
+ if (rect.top > y) return false;
15
+ const center = rect.left + rect.width / 2;
16
+ return rtl ? center > x : center < x;
17
+ }
18
+ export function insertionIndex(elements, x, y, axis, rtl) {
19
+ let index = 0;
20
+ for (const element of elements)
21
+ if (isBefore(element.getBoundingClientRect(), x, y, axis, rtl)) index++;
22
+ return index;
23
+ }
@@ -0,0 +1,26 @@
1
+ import type { Ref } from 'vue';
2
+ export interface ToasterInsets {
3
+ left: number;
4
+ right: number;
5
+ top: number;
6
+ bottom: number;
7
+ }
8
+ export interface ToasterAvoidanceOptions {
9
+ outlet: () => HTMLElement | null;
10
+ toaster: Readonly<Ref<HTMLElement | null | undefined>>;
11
+ position: () => string;
12
+ }
13
+ export declare function useToasterAvoidance(options: ToasterAvoidanceOptions): {
14
+ insets: Ref<{
15
+ left: number;
16
+ right: number;
17
+ top: number;
18
+ bottom: number;
19
+ }, ToasterInsets | {
20
+ left: number;
21
+ right: number;
22
+ top: number;
23
+ bottom: number;
24
+ }>;
25
+ refresh: () => void;
26
+ };
@@ -0,0 +1,134 @@
1
+ import { onScopeDispose, ref, watch } from "vue";
2
+ import { isClient } from "../core/env.mjs";
3
+ import { waitForAnimations } from "../floating/support.mjs";
4
+ const NONE = { left: 0, right: 0, top: 0, bottom: 0 };
5
+ const GAP = 8;
6
+ function sameInsets(a, b) {
7
+ return a.left === b.left && a.right === b.right && a.top === b.top && a.bottom === b.bottom;
8
+ }
9
+ function rootPixels() {
10
+ return parseFloat(getComputedStyle(document.documentElement).fontSize) || 16;
11
+ }
12
+ function drawerSide(dialog) {
13
+ if (!dialog.classList.contains("k-drawer")) return null;
14
+ if (dialog.classList.contains("k-drawer-left")) return "left";
15
+ if (dialog.classList.contains("k-drawer-bottom")) return "bottom";
16
+ return "right";
17
+ }
18
+ function overlapsX(rect, span) {
19
+ return rect.left < span.right && span.left < rect.right;
20
+ }
21
+ function horizontalOf(position) {
22
+ if (position.endsWith("left")) return "left";
23
+ if (position.endsWith("right")) return "right";
24
+ return "center";
25
+ }
26
+ export function useToasterAvoidance(options) {
27
+ const insets = ref(NONE);
28
+ let observer;
29
+ let frame = 0;
30
+ let current = null;
31
+ function sideInsets(dialog, panel, toaster) {
32
+ const side = drawerSide(dialog);
33
+ if (side !== "left" && side !== "right") return null;
34
+ const horizontal = horizontalOf(options.position());
35
+ if (horizontal !== "center" && horizontal !== side) return NONE;
36
+ const style = getComputedStyle(dialog);
37
+ const padding = parseFloat(side === "left" ? style.paddingLeft : style.paddingRight) || 0;
38
+ const occupied = panel.offsetWidth + padding;
39
+ const margin = rootPixels();
40
+ const free = dialog.clientWidth - occupied - 2 * margin;
41
+ if (free < toasterWidth(toaster, dialog.clientWidth)) return null;
42
+ return { ...NONE, [side]: occupied };
43
+ }
44
+ function toasterWidth(toaster, viewport) {
45
+ const width = toaster.offsetWidth || parseFloat(getComputedStyle(toaster).width) || 0;
46
+ return Math.min(width, viewport - 2 * rootPixels());
47
+ }
48
+ function toasterSpan(toaster) {
49
+ const viewport = document.documentElement.clientWidth;
50
+ const margin = rootPixels();
51
+ const width = toasterWidth(toaster, viewport);
52
+ const horizontal = horizontalOf(options.position());
53
+ if (horizontal === "left") return { left: margin, right: margin + width };
54
+ if (horizontal === "right")
55
+ return { left: viewport - margin - width, right: viewport - margin };
56
+ return { left: (viewport - width) / 2, right: (viewport + width) / 2 };
57
+ }
58
+ function verticalInsets(panel, toaster) {
59
+ const bounds = toasterSpan(toaster);
60
+ const viewport = document.documentElement.clientHeight;
61
+ const margin = rootPixels();
62
+ const next = { ...NONE };
63
+ if (options.position().startsWith("top")) {
64
+ const header = panel.querySelector(":scope > header");
65
+ const rect = header?.getBoundingClientRect();
66
+ if (rect && rect.height && overlapsX(rect, bounds) && rect.bottom < viewport / 2)
67
+ next.top = Math.max(0, Math.ceil(rect.bottom + GAP - margin));
68
+ } else {
69
+ const footer = panel.querySelector(":scope > footer");
70
+ const rect = footer?.getBoundingClientRect();
71
+ if (rect && rect.height && overlapsX(rect, bounds) && rect.top > viewport / 2)
72
+ next.bottom = Math.max(0, Math.ceil(viewport - rect.top + GAP - margin));
73
+ }
74
+ return next;
75
+ }
76
+ function compute() {
77
+ frame = 0;
78
+ const dialog = current;
79
+ const toaster = options.toaster.value;
80
+ const panel = dialog?.querySelector(':scope > [class$="-panel"]');
81
+ if (!dialog || !toaster || !panel) {
82
+ if (!dialog && !sameInsets(insets.value, NONE)) insets.value = NONE;
83
+ return;
84
+ }
85
+ const next = sideInsets(dialog, panel, toaster) ?? verticalInsets(panel, toaster);
86
+ if (!sameInsets(next, insets.value)) insets.value = next;
87
+ }
88
+ function schedule() {
89
+ if (!frame) frame = requestAnimationFrame(compute);
90
+ }
91
+ function observe(dialog) {
92
+ observer ??= new ResizeObserver(schedule);
93
+ const panel = dialog.querySelector(':scope > [class$="-panel"]');
94
+ for (const element of [
95
+ dialog,
96
+ panel,
97
+ panel?.querySelector(":scope > header"),
98
+ panel?.querySelector(":scope > footer")
99
+ ])
100
+ if (element) observer.observe(element);
101
+ }
102
+ function disconnect() {
103
+ observer?.disconnect();
104
+ if (frame) cancelAnimationFrame(frame);
105
+ frame = 0;
106
+ }
107
+ watch(
108
+ [options.outlet, options.toaster],
109
+ ([dialog, toaster]) => {
110
+ if (!isClient()) return;
111
+ disconnect();
112
+ current = dialog;
113
+ if (!dialog) {
114
+ insets.value = NONE;
115
+ return;
116
+ }
117
+ observe(dialog);
118
+ if (toaster) observer?.observe(toaster);
119
+ schedule();
120
+ waitForAnimations(dialog).then(() => {
121
+ if (current === dialog) schedule();
122
+ });
123
+ },
124
+ { immediate: true, flush: "post" }
125
+ );
126
+ if (isClient()) {
127
+ window.addEventListener("resize", schedule);
128
+ onScopeDispose(() => {
129
+ window.removeEventListener("resize", schedule);
130
+ disconnect();
131
+ });
132
+ }
133
+ return { insets, refresh: schedule };
134
+ }