poe-svelte-ui-lib 1.9.18 → 1.9.19

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/Modal.svelte CHANGED
@@ -1,8 +1,10 @@
1
1
  <script lang="ts">
2
- import { onMount, type Snippet } from "svelte"
2
+ import { browser } from "$app/environment"
3
+ import { type Snippet } from "svelte"
3
4
  import { fade, scale } from "svelte/transition"
4
5
  import { twMerge } from "tailwind-merge"
5
6
  import CrossIcon from "./libIcons/CrossIcon.svelte"
7
+ import { ModalStack } from "./ModalStackStore"
6
8
 
7
9
  let {
8
10
  isOpen = $bindable(false),
@@ -24,48 +26,77 @@
24
26
  onCancel?: () => void
25
27
  } = $props()
26
28
 
27
- let modalWrapper: HTMLDivElement | null = $state(null)
29
+ let modalId = $state(crypto.randomUUID())
28
30
 
29
- const handleKeyDown = (event: KeyboardEvent) => {
30
- if (event.key === "Escape") {
31
- isOpen = false
32
- onCancel()
31
+ let zIndex = $derived.by(() => {
32
+ const stack = $ModalStack
33
+ const indexInStack = stack.indexOf(modalId)
34
+ return indexInStack !== -1 ? 100 + indexInStack : 100
35
+ })
36
+
37
+ let isTopmost = $derived($ModalStack.at(-1) === modalId)
38
+
39
+ $effect(() => {
40
+ if (!browser) return
41
+
42
+ const handleKeyDown = (e: KeyboardEvent) => {
43
+ if (e.key === "Escape" && isTopmost) {
44
+ isOpen = false
45
+ onCancel()
46
+ }
33
47
  }
34
- }
35
48
 
36
- const handleClickOutside = (event: MouseEvent) => {
37
- if (modalWrapper && !modalWrapper.contains(event.target as Node)) {
38
- isOpen = false
39
- onCancel()
49
+ if (isOpen) {
50
+ ModalStack.open(modalId)
51
+ document.addEventListener("keydown", handleKeyDown)
40
52
  }
41
- }
42
53
 
43
- onMount(() => {
44
- document.addEventListener("keydown", handleKeyDown)
45
- document.addEventListener("mousedown", handleClickOutside)
46
54
  return () => {
47
55
  document.removeEventListener("keydown", handleKeyDown)
48
- document.removeEventListener("mousedown", handleClickOutside)
56
+ ModalStack.close(modalId)
57
+ }
58
+ })
59
+
60
+ $effect(() => {
61
+ if (!browser || !isOpen) return
62
+
63
+ const handleClickOutside = (e: MouseEvent) => {
64
+ const target = e.target as HTMLElement
65
+ if (isTopmost && !target.closest("[data-modal]")) {
66
+ isOpen = false
67
+ onCancel()
68
+ }
49
69
  }
70
+
71
+ document.addEventListener("mousedown", handleClickOutside)
72
+ return () => document.removeEventListener("mousedown", handleClickOutside)
50
73
  })
51
74
  </script>
52
75
 
53
76
  {#if isOpen}
54
- <div class="fixed inset-0 z-100 flex items-center justify-center bg-black/50" transition:fade={{ duration: 200, delay: 1 }}>
77
+ <div
78
+ class="fixed inset-0 flex items-center justify-center bg-black/50"
79
+ data-modal-backdrop
80
+ transition:fade={{ duration: 200 }}
81
+ style="z-index: {zIndex - 1};"
82
+ >
55
83
  <div
56
- bind:this={modalWrapper}
57
- class={twMerge(`flex w-300 flex-col overflow-hidden rounded-2xl bg-(--back-color) text-center`, wrapperClass)}
58
- style="width: {width};"
84
+ data-modal
85
+ class={twMerge("flex w-300 flex-col overflow-hidden rounded-2xl bg-(--back-color) text-center", wrapperClass)}
86
+ style="width: {width}; z-index: {zIndex};"
59
87
  transition:scale={{ duration: 250, start: 0.8 }}
60
88
  >
61
89
  <div class="flex items-end justify-between bg-(--field-color) px-6 py-3">
62
90
  <h4>{title}</h4>
63
- <button class="h-6 w-6 cursor-pointer" onclick={onCancel}> <CrossIcon /> </button>
91
+ <button class="h-6 w-6 cursor-pointer" onclick={onCancel}>
92
+ <CrossIcon />
93
+ </button>
64
94
  </div>
65
95
 
66
96
  <div class={twMerge("flex h-full w-full flex-col overflow-auto p-2", mainClass)}>
67
97
  {@render main?.()}
68
98
  </div>
99
+
69
100
  {#if footer}
70
101
  <div class="flex flex-row-reverse justify-between bg-(--field-color) p-1.5">
71
102
  {@render footer?.()}
@@ -0,0 +1,7 @@
1
+ export declare const ModalStack: {
2
+ subscribe: (this: void, run: import("svelte/store").Subscriber<string[]>, invalidate?: () => void) => import("svelte/store").Unsubscriber;
3
+ open: (id: string) => void;
4
+ close: (id: string) => void;
5
+ getTopmost: () => string;
6
+ isInStack: (id: string) => boolean;
7
+ };
@@ -0,0 +1,19 @@
1
+ import { writable, get } from "svelte/store";
2
+ const ModalStore = writable([]);
3
+ export const ModalStack = {
4
+ subscribe: ModalStore.subscribe,
5
+ open: (id) => {
6
+ ModalStore.update((state) => [...state, id]);
7
+ },
8
+ close: (id) => {
9
+ ModalStore.update((state) => state.filter((item) => item !== id));
10
+ },
11
+ getTopmost: () => {
12
+ const state = get(ModalStore);
13
+ return state[state.length - 1];
14
+ },
15
+ isInStack: (id) => {
16
+ const state = get(ModalStore);
17
+ return state.includes(id);
18
+ },
19
+ };
@@ -17,7 +17,7 @@
17
17
  const isRange = $derived(type === "range" || (Array.isArray(value) && value.length === 2))
18
18
 
19
19
  const maxDigits = $derived(String(number.maxNum ?? 10).length)
20
- const valueWidth = $derived(`${maxDigits + 1}ch`) /* +1 на запас */
20
+ const valueWidth = $derived(`${maxDigits + 1}ch`)
21
21
 
22
22
  /* Инициализация значений с проверкой типа */
23
23
  let singleValue = $derived(!isRange && typeof value === "number" ? value : number.minNum)
package/dist/index.d.ts CHANGED
@@ -35,4 +35,5 @@ export { default as Widget } from "./Widget/Widget.svelte";
35
35
  export { default as WidgetProps } from "./Widget/WidgetProps.svelte";
36
36
  export * from "./locales/i18n";
37
37
  export * from "./locales/translations";
38
+ export * from "./ModalStackStore";
38
39
  export { type UIComponent, type Position, type IUIComponentHandler, type IButtonProps, type IAccordionProps, type IInputProps, type ISelectProps, type IOption, type ISwitchProps, type IColorPickerProps, type ISliderProps, type ITextFieldProps, type IMapProps, type IProgressBarProps, type IWidgetProps, type IGraphProps, type IGraphDataObject, type ITableHeader, type ITableProps, type ITabsProps, type IJoystickProps, type IFileAttachProps, type IDeviceGNSS, type ICarouselProps, type IReceivingDataObject, } from "./types";
package/dist/index.js CHANGED
@@ -36,4 +36,5 @@ export { default as Widget } from "./Widget/Widget.svelte";
36
36
  export { default as WidgetProps } from "./Widget/WidgetProps.svelte";
37
37
  export * from "./locales/i18n";
38
38
  export * from "./locales/translations";
39
+ export * from "./ModalStackStore";
39
40
  export {} from "./types";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "poe-svelte-ui-lib",
3
- "version": "1.9.18",
3
+ "version": "1.9.19",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "scripts": {
@@ -33,27 +33,27 @@
33
33
  "svelte": "^5.0.0"
34
34
  },
35
35
  "dependencies": {
36
- "tailwind-merge": "^3.5.0"
36
+ "tailwind-merge": "^3.6.0"
37
37
  },
38
38
  "devDependencies": {
39
39
  "@sveltejs/adapter-static": "^3.0.10",
40
40
  "@sveltejs/kit": "^2.59.1",
41
41
  "@sveltejs/package": "^2.5.7",
42
- "@sveltejs/vite-plugin-svelte": "^7.1.1",
43
- "@tailwindcss/vite": "^4.2.4",
42
+ "@sveltejs/vite-plugin-svelte": "^7.1.2",
43
+ "@tailwindcss/vite": "^4.3.0",
44
44
  "@types/maplibre-gl": "^1.14.0",
45
- "@types/node": "^25.6.0",
45
+ "@types/node": "^25.7.0",
46
46
  "dompurify": "^3.4.2",
47
47
  "marked": "^18.0.3",
48
48
  "prettier": "^3.8.3",
49
- "prettier-plugin-svelte": "^3.5.1",
49
+ "prettier-plugin-svelte": "^3.5.2",
50
50
  "prettier-plugin-tailwindcss": "^0.8.0",
51
- "publint": "^0.3.19",
51
+ "publint": "^0.3.21",
52
52
  "svelte": "^5.55.5",
53
- "tailwindcss": "^4.2.4",
53
+ "tailwindcss": "^4.3.0",
54
54
  "tsx": "^4.21.0",
55
55
  "typescript": "^6.0.3",
56
- "vite": "^8.0.10",
56
+ "vite": "^8.0.12",
57
57
  "vite-plugin-compression": "^0.5.1"
58
58
  }
59
59
  }