@simplysm/solid 13.0.57 → 13.0.59

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 (105) hide show
  1. package/README.md +1 -1
  2. package/dist/components/data/crud-detail/CrudDetail.d.ts.map +1 -1
  3. package/dist/components/data/crud-detail/CrudDetail.js +55 -42
  4. package/dist/components/data/crud-detail/CrudDetail.js.map +2 -2
  5. package/dist/components/data/crud-sheet/CrudSheet.d.ts.map +1 -1
  6. package/dist/components/data/crud-sheet/CrudSheet.js +120 -94
  7. package/dist/components/data/crud-sheet/CrudSheet.js.map +2 -2
  8. package/dist/components/data/crud-sheet/CrudSheetColumn.js +1 -1
  9. package/dist/components/data/crud-sheet/CrudSheetColumn.js.map +2 -2
  10. package/dist/components/data/crud-sheet/types.d.ts +4 -3
  11. package/dist/components/data/crud-sheet/types.d.ts.map +1 -1
  12. package/dist/components/data/kanban/Kanban.d.ts.map +1 -1
  13. package/dist/components/data/kanban/Kanban.js +3 -4
  14. package/dist/components/data/kanban/Kanban.js.map +2 -2
  15. package/dist/components/data/kanban/KanbanContext.d.ts +2 -3
  16. package/dist/components/data/kanban/KanbanContext.d.ts.map +1 -1
  17. package/dist/components/data/kanban/KanbanContext.js.map +1 -1
  18. package/dist/components/data/list/ListItem.d.ts.map +1 -1
  19. package/dist/components/data/list/ListItem.js +3 -3
  20. package/dist/components/data/list/ListItem.js.map +2 -2
  21. package/dist/components/data/sheet/DataSheet.styles.d.ts.map +1 -1
  22. package/dist/components/data/sheet/DataSheet.styles.js +3 -8
  23. package/dist/components/data/sheet/DataSheet.styles.js.map +1 -1
  24. package/dist/components/disclosure/Dialog.d.ts.map +1 -1
  25. package/dist/components/disclosure/Dialog.js +36 -27
  26. package/dist/components/disclosure/Dialog.js.map +2 -2
  27. package/dist/components/disclosure/Dropdown.d.ts.map +1 -1
  28. package/dist/components/disclosure/Dropdown.js +7 -15
  29. package/dist/components/disclosure/Dropdown.js.map +2 -2
  30. package/dist/components/display/Icon.js +1 -1
  31. package/dist/components/display/Icon.js.map +1 -1
  32. package/dist/components/feedback/notification/NotificationBanner.js +1 -1
  33. package/dist/components/feedback/notification/NotificationBanner.js.map +1 -1
  34. package/dist/components/feedback/notification/NotificationContext.d.ts +1 -1
  35. package/dist/components/feedback/notification/NotificationContext.d.ts.map +1 -1
  36. package/dist/components/feedback/notification/NotificationContext.js.map +1 -1
  37. package/dist/components/feedback/notification/NotificationProvider.d.ts.map +1 -1
  38. package/dist/components/feedback/notification/NotificationProvider.js +8 -12
  39. package/dist/components/feedback/notification/NotificationProvider.js.map +2 -2
  40. package/dist/components/form-control/color-picker/ColorPicker.d.ts +5 -3
  41. package/dist/components/form-control/color-picker/ColorPicker.d.ts.map +1 -1
  42. package/dist/components/form-control/color-picker/ColorPicker.js +11 -6
  43. package/dist/components/form-control/color-picker/ColorPicker.js.map +2 -2
  44. package/dist/components/form-control/field/NumberInput.d.ts.map +1 -1
  45. package/dist/components/form-control/field/NumberInput.js +2 -2
  46. package/dist/components/form-control/field/NumberInput.js.map +2 -2
  47. package/dist/components/form-control/field/TextInput.d.ts.map +1 -1
  48. package/dist/components/form-control/field/TextInput.js +3 -3
  49. package/dist/components/form-control/field/TextInput.js.map +2 -2
  50. package/dist/components/form-control/select/Select.d.ts.map +1 -1
  51. package/dist/components/form-control/select/Select.js +3 -4
  52. package/dist/components/form-control/select/Select.js.map +2 -2
  53. package/dist/components/form-control/select/SelectContext.d.ts +1 -2
  54. package/dist/components/form-control/select/SelectContext.d.ts.map +1 -1
  55. package/dist/components/form-control/select/SelectContext.js.map +1 -1
  56. package/dist/components/form-control/select/SelectItem.d.ts.map +1 -1
  57. package/dist/components/form-control/select/SelectItem.js +3 -3
  58. package/dist/components/form-control/select/SelectItem.js.map +2 -2
  59. package/dist/helpers/createAppStructure.d.ts +7 -4
  60. package/dist/helpers/createAppStructure.d.ts.map +1 -1
  61. package/dist/helpers/createAppStructure.js +20 -2
  62. package/dist/helpers/createAppStructure.js.map +1 -1
  63. package/dist/hooks/createPointerDrag.d.ts +1 -1
  64. package/dist/hooks/createPointerDrag.d.ts.map +1 -1
  65. package/dist/hooks/createPointerDrag.js +6 -4
  66. package/dist/hooks/createPointerDrag.js.map +1 -1
  67. package/dist/hooks/createSlotSignal.d.ts +9 -0
  68. package/dist/hooks/createSlotSignal.d.ts.map +1 -0
  69. package/dist/hooks/createSlotSignal.js +10 -0
  70. package/dist/hooks/createSlotSignal.js.map +6 -0
  71. package/dist/index.d.ts +15 -17
  72. package/dist/index.d.ts.map +1 -1
  73. package/dist/index.js +15 -39
  74. package/dist/index.js.map +1 -1
  75. package/docs/data-components.md +18 -8
  76. package/docs/feedback.md +17 -10
  77. package/docs/helpers.md +24 -0
  78. package/docs/hooks.md +166 -83
  79. package/docs/styling.md +1 -0
  80. package/package.json +3 -3
  81. package/src/components/data/crud-detail/CrudDetail.tsx +45 -40
  82. package/src/components/data/crud-sheet/CrudSheet.tsx +99 -103
  83. package/src/components/data/crud-sheet/CrudSheetColumn.tsx +1 -1
  84. package/src/components/data/crud-sheet/types.ts +4 -3
  85. package/src/components/data/kanban/Kanban.tsx +3 -5
  86. package/src/components/data/kanban/KanbanContext.ts +2 -3
  87. package/src/components/data/list/ListItem.tsx +2 -5
  88. package/src/components/data/sheet/DataSheet.styles.ts +3 -8
  89. package/src/components/disclosure/Dialog.tsx +26 -26
  90. package/src/components/disclosure/Dropdown.tsx +7 -20
  91. package/src/components/display/Icon.tsx +1 -1
  92. package/src/components/feedback/notification/NotificationBanner.tsx +1 -1
  93. package/src/components/feedback/notification/NotificationContext.ts +2 -7
  94. package/src/components/feedback/notification/NotificationProvider.tsx +8 -15
  95. package/src/components/form-control/color-picker/ColorPicker.tsx +19 -9
  96. package/src/components/form-control/field/NumberInput.tsx +2 -4
  97. package/src/components/form-control/field/TextInput.tsx +2 -5
  98. package/src/components/form-control/select/Select.tsx +3 -6
  99. package/src/components/form-control/select/SelectContext.ts +1 -2
  100. package/src/components/form-control/select/SelectItem.tsx +2 -5
  101. package/src/helpers/createAppStructure.ts +36 -6
  102. package/src/hooks/createPointerDrag.ts +8 -5
  103. package/src/hooks/createSlotSignal.ts +14 -0
  104. package/src/index.ts +15 -41
  105. package/tailwind.config.ts +1 -0
@@ -10,6 +10,8 @@ import {
10
10
  splitProps,
11
11
  } from "solid-js";
12
12
  import { createResizeObserver } from "@solid-primitives/resize-observer";
13
+ import { createControllableSignal } from "../../hooks/createControllableSignal";
14
+ import { createSlotSignal, type SlotAccessor } from "../../hooks/createSlotSignal";
13
15
  import { createMountTransition } from "../../hooks/createMountTransition";
14
16
  import { Portal } from "solid-js/web";
15
17
  import clsx from "clsx";
@@ -19,8 +21,6 @@ import { borderSubtle } from "../../styles/tokens.styles";
19
21
 
20
22
  // --- DropdownContext (internal) ---
21
23
 
22
- type SlotAccessor = (() => JSX.Element) | undefined;
23
-
24
24
  interface DropdownContextValue {
25
25
  toggle: () => void;
26
26
  setTrigger: (content: SlotAccessor) => void;
@@ -154,22 +154,11 @@ export const Dropdown: DropdownComponent = ((props: DropdownProps) => {
154
154
  "children",
155
155
  ]);
156
156
 
157
- const [open, setOpenInternal] = createSignal(false);
158
-
159
- // props.open 변경 시 내부 상태 동기화
160
- createEffect(() => {
161
- const propOpen = local.open;
162
- if (propOpen !== undefined) {
163
- setOpenInternal(propOpen);
164
- }
157
+ const [open, setOpen] = createControllableSignal({
158
+ value: () => local.open ?? false,
159
+ onChange: () => local.onOpenChange,
165
160
  });
166
161
 
167
- // 콜백 포함 setter
168
- const setOpen = (value: boolean) => {
169
- setOpenInternal(value);
170
- local.onOpenChange?.(value);
171
- };
172
-
173
162
  // toggle 함수 (disabled 체크 포함)
174
163
  const toggle = () => {
175
164
  if (local.disabled) return;
@@ -177,10 +166,8 @@ export const Dropdown: DropdownComponent = ((props: DropdownProps) => {
177
166
  };
178
167
 
179
168
  // 슬롯 등록 시그널
180
- const [triggerSlot, _setTriggerSlot] = createSignal<SlotAccessor>();
181
- const setTrigger = (content: SlotAccessor) => _setTriggerSlot(() => content);
182
- const [contentSlot, _setContentSlot] = createSignal<SlotAccessor>();
183
- const setContent = (content: SlotAccessor) => _setContentSlot(() => content);
169
+ const [triggerSlot, setTrigger] = createSlotSignal();
170
+ const [contentSlot, setContent] = createSlotSignal();
184
171
 
185
172
  // Trigger wrapper ref (위치 계산에 필요)
186
173
  let triggerRef: HTMLDivElement | undefined;
@@ -16,7 +16,7 @@ export const Icon: Component<IconProps> = (props) => {
16
16
  component={local.icon}
17
17
  size={local.size ?? "1.25em"}
18
18
  {...rest}
19
- class={clsx("inline", rest.class)}
19
+ class={clsx("inline", "[vertical-align:-0.25em]", rest.class)}
20
20
  />
21
21
  );
22
22
  };
@@ -10,7 +10,7 @@ const baseClass = clsx(
10
10
  "fixed",
11
11
  "top-8",
12
12
  "right-4",
13
- "z-50",
13
+ "z-notification",
14
14
  "flex",
15
15
  "items-start",
16
16
  "gap-4",
@@ -1,4 +1,4 @@
1
- import { createContext, useContext, type Accessor } from "solid-js";
1
+ import { type Accessor, createContext, useContext } from "solid-js";
2
2
 
3
3
  /** 알림 테마 */
4
4
  export type NotificationTheme = "info" | "success" | "warning" | "danger";
@@ -59,12 +59,7 @@ export interface NotificationContextValue {
59
59
  success: (title: string, message?: string, options?: NotificationOptions) => string;
60
60
  warning: (title: string, message?: string, options?: NotificationOptions) => string;
61
61
  danger: (title: string, message?: string, options?: NotificationOptions) => string;
62
-
63
- // 에러 처리 (danger 알림 + 로깅)
64
- try: <TResult>(
65
- fn: () => Promise<TResult> | TResult,
66
- header?: string,
67
- ) => Promise<TResult | undefined>;
62
+ error: (err?: any, header?: string) => void;
68
63
 
69
64
  // 알림 수정
70
65
  update: (
@@ -1,4 +1,4 @@
1
- import { type ParentComponent, createSignal, createMemo, Show } from "solid-js";
1
+ import { createMemo, createSignal, type ParentComponent, Show } from "solid-js";
2
2
  import {
3
3
  NotificationContext,
4
4
  type NotificationContextValue,
@@ -79,20 +79,13 @@ export const NotificationProvider: ParentComponent = (props) => {
79
79
  return addNotification("danger", title, message, options);
80
80
  };
81
81
 
82
- const tryFn = async <TResult,>(
83
- fn: () => Promise<TResult> | TResult,
84
- header?: string,
85
- ): Promise<TResult | undefined> => {
86
- try {
87
- return await fn();
88
- } catch (err) {
89
- if (err instanceof Error) {
90
- danger(header ?? err.message, header != null ? err.message : undefined);
91
- logger.error(err.stack ?? err.message);
92
- return undefined;
93
- }
94
- throw err;
82
+ const error = (err?: any, header?: string): void => {
83
+ if (err instanceof Error) {
84
+ danger(header ?? err.message, header != null ? err.message : undefined);
85
+ logger.error(err.stack ?? err.message);
86
+ return;
95
87
  }
88
+ throw err;
96
89
  };
97
90
 
98
91
  const update = (
@@ -151,7 +144,7 @@ export const NotificationProvider: ParentComponent = (props) => {
151
144
  success,
152
145
  warning,
153
146
  danger,
154
- try: tryFn,
147
+ error,
155
148
  update,
156
149
  remove,
157
150
  markAsRead,
@@ -3,7 +3,7 @@ import clsx from "clsx";
3
3
  import { twMerge } from "tailwind-merge";
4
4
  import { createControllableSignal } from "../../../hooks/createControllableSignal";
5
5
  import { Invalid } from "../Invalid";
6
- import { type ComponentSizeCompact } from "../../../styles/tokens.styles";
6
+ import { type ComponentSize } from "../../../styles/tokens.styles";
7
7
 
8
8
  // 기본 스타일
9
9
  const baseClass = clsx(
@@ -20,13 +20,15 @@ const baseClass = clsx(
20
20
  );
21
21
 
22
22
  // 사이즈별 스타일
23
- const sizeClasses: Record<ComponentSizeCompact, string> = {
23
+ const sizeClasses: Record<ComponentSize, string> = {
24
+ xs: "size-field-xs",
24
25
  sm: "size-field-sm",
25
26
  lg: "size-field-lg",
27
+ xl: "size-field-xl",
26
28
  };
27
29
 
28
30
  // disabled 스타일 - 대각선 줄무늬로 표시
29
- // eslint-disable-next-line tailwindcss/enforces-shorthand -- inset은 Chrome 87+에서만 지원
31
+ // eslint-disable-next-line tailwindcss/enforces-shorthand
30
32
  const disabledClass = clsx(
31
33
  "cursor-default",
32
34
  "relative",
@@ -39,7 +41,7 @@ export interface ColorPickerProps {
39
41
  value?: string;
40
42
 
41
43
  /** 값 변경 콜백 */
42
- onValueChange?: (value: string) => void;
44
+ onValueChange?: (value: string | undefined) => void;
43
45
 
44
46
  /** 타이틀 (툴팁) */
45
47
  title?: string;
@@ -48,7 +50,10 @@ export interface ColorPickerProps {
48
50
  disabled?: boolean;
49
51
 
50
52
  /** 사이즈 */
51
- size?: ComponentSizeCompact;
53
+ size?: ComponentSize;
54
+
55
+ /** inset 모드 (DataSheet 셀 내부 등) */
56
+ inset?: boolean;
52
57
 
53
58
  /** 필수 입력 여부 */
54
59
  required?: boolean;
@@ -81,6 +86,7 @@ export const ColorPicker: Component<ColorPickerProps> = (props) => {
81
86
  "title",
82
87
  "disabled",
83
88
  "size",
89
+ "inset",
84
90
  "required",
85
91
  "validate",
86
92
  "touchMode",
@@ -89,7 +95,7 @@ export const ColorPicker: Component<ColorPickerProps> = (props) => {
89
95
  ]);
90
96
 
91
97
  const [value, setValue] = createControllableSignal({
92
- value: () => local.value ?? "#000000",
98
+ value: () => local.value,
93
99
  onChange: () => local.onValueChange,
94
100
  });
95
101
 
@@ -106,20 +112,24 @@ export const ColorPicker: Component<ColorPickerProps> = (props) => {
106
112
  );
107
113
 
108
114
  const errorMsg = createMemo(() => {
109
- const v = props.value;
115
+ const v = value();
110
116
  if (local.required && (v === undefined || v === "")) return "필수 입력 항목입니다";
111
117
  return local.validate?.(v);
112
118
  });
113
119
 
114
120
  return (
115
- <Invalid variant="border" message={errorMsg()} touchMode={local.touchMode}>
121
+ <Invalid
122
+ variant={local.inset ? "dot" : "border"}
123
+ message={errorMsg()}
124
+ touchMode={local.touchMode}
125
+ >
116
126
  <input
117
127
  {...rest}
118
128
  data-color-picker
119
129
  type="color"
120
130
  class={getClassName()}
121
131
  style={local.style}
122
- value={value()}
132
+ value={value() ?? "#000000"}
123
133
  title={local.title}
124
134
  disabled={local.disabled}
125
135
  onInput={handleInput}
@@ -13,6 +13,7 @@ import {
13
13
  import clsx from "clsx";
14
14
  import { twMerge } from "tailwind-merge";
15
15
  import { createControllableSignal } from "../../../hooks/createControllableSignal";
16
+ import { createSlotSignal, type SlotAccessor } from "../../../hooks/createSlotSignal";
16
17
  import {
17
18
  type FieldSize,
18
19
  fieldInputClass,
@@ -30,8 +31,6 @@ const numberInputClass = clsx(
30
31
  "[&::-webkit-inner-spin-button]:appearance-none",
31
32
  );
32
33
 
33
- type SlotAccessor = (() => JSX.Element) | undefined;
34
-
35
34
  interface NumberInputSlotsContextValue {
36
35
  setPrefix: (content: SlotAccessor) => void;
37
36
  }
@@ -235,8 +234,7 @@ export const NumberInput: NumberInputComponent = (props) => {
235
234
  onChange: () => local.onValueChange,
236
235
  });
237
236
 
238
- const [prefix, _setPrefix] = createSignal<SlotAccessor>();
239
- const setPrefix = (content: SlotAccessor) => _setPrefix(() => content);
237
+ const [prefix, setPrefix] = createSlotSignal();
240
238
  const prefixEl = () => prefix() !== undefined;
241
239
 
242
240
  // 외부 값 변경 시 입력 문자열 동기화
@@ -3,7 +3,6 @@ import {
3
3
  createContext,
4
4
  createEffect,
5
5
  createMemo,
6
- createSignal,
7
6
  type JSX,
8
7
  onCleanup,
9
8
  type ParentComponent,
@@ -13,6 +12,7 @@ import {
13
12
  } from "solid-js";
14
13
  import { twMerge } from "tailwind-merge";
15
14
  import { createControllableSignal } from "../../../hooks/createControllableSignal";
15
+ import { createSlotSignal, type SlotAccessor } from "../../../hooks/createSlotSignal";
16
16
  import { createIMEHandler } from "../../../hooks/createIMEHandler";
17
17
  import {
18
18
  fieldGapClasses,
@@ -23,8 +23,6 @@ import {
23
23
  import { PlaceholderFallback } from "./FieldPlaceholder";
24
24
  import { Invalid } from "../../form-control/Invalid";
25
25
 
26
- type SlotAccessor = (() => JSX.Element) | undefined;
27
-
28
26
  interface TextInputSlotsContextValue {
29
27
  setPrefix: (content: SlotAccessor) => void;
30
28
  }
@@ -244,8 +242,7 @@ const TextInputInner = (props: TextInputProps) => {
244
242
  };
245
243
 
246
244
  // Prefix 슬롯 Context 등록
247
- const [prefix, _setPrefix] = createSignal<SlotAccessor>();
248
- const setPrefix = (content: SlotAccessor) => _setPrefix(() => content);
245
+ const [prefix, setPrefix] = createSlotSignal();
249
246
  const prefixEl = () => prefix() !== undefined;
250
247
 
251
248
  // wrapper 클래스 (includeCustomClass=false일 때 local.class 제외 — inset에서 outer에만 적용)
@@ -21,13 +21,12 @@ import { SelectItem } from "./SelectItem";
21
21
  import { ripple } from "../../../directives/ripple";
22
22
  import { borderDefault, type ComponentSize, textMuted } from "../../../styles/tokens.styles";
23
23
  import { createControllableSignal } from "../../../hooks/createControllableSignal";
24
+ import { createSlotSignal } from "../../../hooks/createSlotSignal";
24
25
  import { chevronWrapperClass, getTriggerClass } from "../DropdownTrigger.styles";
25
26
  import { Invalid } from "../Invalid";
26
27
 
27
28
  void ripple;
28
29
 
29
- type SlotAccessor = (() => JSX.Element) | undefined;
30
-
31
30
  // Select 전용 스타일
32
31
  const multiTagClass = clsx("rounded", "bg-base-200 px-1", "dark:bg-base-600");
33
32
  const selectedValueClass = clsx("flex-1", "whitespace-nowrap");
@@ -272,10 +271,8 @@ export const Select: SelectComponent = <T,>(props: SelectProps<T>) => {
272
271
  };
273
272
 
274
273
  // 슬롯 signals
275
- const [header, _setHeader] = createSignal<SlotAccessor>();
276
- const setHeader = (content: SlotAccessor) => _setHeader(() => content);
277
- const [action, _setAction] = createSignal<SlotAccessor>();
278
- const setAction = (content: SlotAccessor) => _setAction(() => content);
274
+ const [header, setHeader] = createSlotSignal();
275
+ const [action, setAction] = createSlotSignal();
279
276
  const [itemTemplate, _setItemTemplate] = createSignal<
280
277
  ((...args: unknown[]) => JSX.Element) | undefined
281
278
  >();
@@ -1,6 +1,5 @@
1
1
  import { createContext, useContext, type Accessor, type JSX } from "solid-js";
2
-
3
- type SlotAccessor = (() => JSX.Element) | undefined;
2
+ import type { SlotAccessor } from "../../../hooks/createSlotSignal";
4
3
 
5
4
  export interface SelectContextValue<TValue = unknown> {
6
5
  /** 다중 선택 모드 여부 */
@@ -1,6 +1,5 @@
1
1
  import {
2
2
  createContext,
3
- createSignal,
4
3
  type JSX,
5
4
  onCleanup,
6
5
  type ParentComponent,
@@ -13,6 +12,7 @@ import { IconCheck } from "@tabler/icons-solidjs";
13
12
  import { Icon } from "../../display/Icon";
14
13
  import { useSelectContext } from "./SelectContext";
15
14
  import { ripple } from "../../../directives/ripple";
15
+ import { createSlotSignal, type SlotAccessor } from "../../../hooks/createSlotSignal";
16
16
  import { List } from "../../data/list/List";
17
17
  import { Collapse } from "../../disclosure/Collapse";
18
18
  import {
@@ -26,8 +26,6 @@ import {
26
26
 
27
27
  void ripple;
28
28
 
29
- type SlotAccessor = (() => JSX.Element) | undefined;
30
-
31
29
  interface SelectItemSlotsContextValue {
32
30
  setChildren: (content: SlotAccessor) => void;
33
31
  }
@@ -83,8 +81,7 @@ export const SelectItem: SelectItemComponent = <T,>(
83
81
 
84
82
  const context = useSelectContext<T>();
85
83
 
86
- const [childrenSlot, _setChildrenSlot] = createSignal<SlotAccessor>();
87
- const setChildrenSlot = (content: SlotAccessor) => _setChildrenSlot(() => content);
84
+ const [childrenSlot, setChildrenSlot] = createSlotSignal();
88
85
  const hasChildren = () => childrenSlot() !== undefined;
89
86
  const isSelected = () => context.isSelected(local.value);
90
87
  const useRipple = () => !local.disabled;
@@ -1,5 +1,5 @@
1
- import type { Component } from "solid-js";
2
- import { type Accessor, createMemo, createRoot } from "solid-js";
1
+ import type { Component, ParentComponent } from "solid-js";
2
+ import { type Accessor, createContext, createMemo, createRoot, useContext } from "solid-js";
3
3
  import type { IconProps } from "@tabler/icons-solidjs";
4
4
 
5
5
  // ── 입력 타입 ──
@@ -412,10 +412,7 @@ function findItemChainByCodes<TModule>(
412
412
 
413
413
  // ── 메인 함수 ──
414
414
 
415
- export function createAppStructure<
416
- TModule,
417
- const TItems extends AppStructureItem<TModule>[],
418
- >(opts: {
415
+ function buildAppStructure<TModule, const TItems extends AppStructureItem<TModule>[]>(opts: {
419
416
  items: TItems;
420
417
  usableModules?: Accessor<TModule[] | undefined>;
421
418
  permRecord?: Accessor<Record<string, boolean> | undefined>;
@@ -481,3 +478,36 @@ export function createAppStructure<
481
478
  },
482
479
  };
483
480
  }
481
+
482
+ export function createAppStructure<TModule, const TItems extends AppStructureItem<TModule>[]>(
483
+ getOpts: () => {
484
+ items: TItems;
485
+ usableModules?: Accessor<TModule[] | undefined>;
486
+ permRecord?: Accessor<Record<string, boolean> | undefined>;
487
+ },
488
+ ): {
489
+ AppStructureProvider: ParentComponent;
490
+ useAppStructure: () => AppStructure<TModule> & { perms: InferPerms<TItems> };
491
+ } {
492
+ type TRet = AppStructure<TModule> & { perms: InferPerms<TItems> };
493
+
494
+ const Ctx = createContext<TRet>();
495
+
496
+ const AppStructureProvider: ParentComponent = (props) => {
497
+ const structure = buildAppStructure(getOpts());
498
+ return Ctx.Provider({
499
+ value: structure as TRet,
500
+ get children() {
501
+ return props.children;
502
+ },
503
+ });
504
+ };
505
+
506
+ const useAppStructure = (): TRet => {
507
+ const ctx = useContext(Ctx);
508
+ if (!ctx) throw new Error("AppStructureProvider가 필요합니다.");
509
+ return ctx;
510
+ };
511
+
512
+ return { AppStructureProvider, useAppStructure };
513
+ }
@@ -4,7 +4,7 @@
4
4
  * @param target - Element to capture pointer on
5
5
  * @param pointerId - Pointer ID from the initiating PointerEvent
6
6
  * @param options.onMove - Called on each pointermove
7
- * @param options.onEnd - Called on pointerup (after listener cleanup)
7
+ * @param options.onEnd - Called on pointerup or pointercancel (after listener cleanup)
8
8
  */
9
9
  export function createPointerDrag(
10
10
  target: HTMLElement,
@@ -16,13 +16,16 @@ export function createPointerDrag(
16
16
  ): void {
17
17
  target.setPointerCapture(pointerId);
18
18
 
19
- const onPointerMove = (e: PointerEvent) => options.onMove(e);
20
- const onPointerUp = (e: PointerEvent) => {
19
+ const cleanup = (e: PointerEvent) => {
21
20
  target.removeEventListener("pointermove", onPointerMove);
22
- target.removeEventListener("pointerup", onPointerUp);
21
+ target.removeEventListener("pointerup", cleanup);
22
+ target.removeEventListener("pointercancel", cleanup);
23
23
  options.onEnd(e);
24
24
  };
25
25
 
26
+ const onPointerMove = (e: PointerEvent) => options.onMove(e);
27
+
26
28
  target.addEventListener("pointermove", onPointerMove);
27
- target.addEventListener("pointerup", onPointerUp);
29
+ target.addEventListener("pointerup", cleanup);
30
+ target.addEventListener("pointercancel", cleanup);
28
31
  }
@@ -0,0 +1,14 @@
1
+ import { createSignal, type Accessor, type JSX } from "solid-js";
2
+
3
+ export type SlotAccessor = (() => JSX.Element) | undefined;
4
+
5
+ /**
6
+ * 슬롯 등록용 signal 생성
7
+ *
8
+ * @returns [accessor, setter] — setter는 함수를 값으로 저장하기 위해 래핑 처리
9
+ */
10
+ export function createSlotSignal(): [Accessor<SlotAccessor>, (content: SlotAccessor) => void] {
11
+ const [slot, _setSlot] = createSignal<SlotAccessor>();
12
+ const setSlot = (content: SlotAccessor) => _setSlot(() => content);
13
+ return [slot, setSlot];
14
+ }
package/src/index.ts CHANGED
@@ -115,37 +115,22 @@ export * from "./components/feedback/Progress";
115
115
  //#region ========== Providers ==========
116
116
 
117
117
  // Config
118
- export { type AppConfig, ConfigContext, useConfig } from "./providers/ConfigContext";
118
+ export * from "./providers/ConfigContext";
119
119
 
120
120
  // SyncStorage
121
- export {
122
- type StorageAdapter,
123
- type SyncStorageContextValue,
124
- SyncStorageContext,
125
- useSyncStorage,
126
- } from "./providers/SyncStorageContext";
121
+ export * from "./providers/SyncStorageContext";
127
122
 
128
123
  // Logger
129
- export { type LogAdapter, type LoggerContextValue } from "./providers/LoggerContext";
124
+ export * from "./providers/LoggerContext";
130
125
 
131
126
  // Theme
132
- export { useTheme } from "./providers/ThemeContext";
133
- export type { ThemeMode, ResolvedTheme } from "./providers/ThemeContext";
127
+ export * from "./providers/ThemeContext";
134
128
 
135
129
  // ServiceClient
136
- export {
137
- type ServiceClientContextValue,
138
- ServiceClientContext,
139
- useServiceClient,
140
- } from "./providers/ServiceClientContext";
130
+ export * from "./providers/ServiceClientContext";
141
131
 
142
132
  // SharedData
143
- export type {
144
- SharedDataDefinition,
145
- SharedDataAccessor,
146
- SharedDataValue,
147
- } from "./providers/shared-data/SharedDataContext";
148
- export { SharedDataContext, useSharedData } from "./providers/shared-data/SharedDataContext";
133
+ export * from "./providers/shared-data/SharedDataContext";
149
134
  export * from "./providers/shared-data/SharedDataChangeEvent";
150
135
 
151
136
  // SystemProvider
@@ -158,11 +143,12 @@ export * from "./providers/SystemProvider";
158
143
  export * from "./hooks/useLocalStorage";
159
144
  export * from "./hooks/useSyncConfig";
160
145
  export * from "./hooks/useLogger";
161
- export { createControllableSignal } from "./hooks/createControllableSignal";
162
- export { createControllableStore } from "./hooks/createControllableStore";
163
- export { createIMEHandler } from "./hooks/createIMEHandler";
164
- export { createMountTransition } from "./hooks/createMountTransition";
165
- export { useRouterLink } from "./hooks/useRouterLink";
146
+ export * from "./hooks/createControllableSignal";
147
+ export * from "./hooks/createControllableStore";
148
+ export * from "./hooks/createIMEHandler";
149
+ export * from "./hooks/createMountTransition";
150
+ export * from "./hooks/createSlotSignal";
151
+ export * from "./hooks/useRouterLink";
166
152
 
167
153
  //#endregion
168
154
 
@@ -175,25 +161,13 @@ export * from "./styles/patterns.styles";
175
161
 
176
162
  //#region ========== Directives ==========
177
163
 
178
- export { ripple } from "./directives/ripple";
164
+ export * from "./directives/ripple";
179
165
 
180
166
  //#endregion
181
167
 
182
168
  //#region ========== Helpers ==========
183
169
 
184
- export { mergeStyles } from "./helpers/mergeStyles";
185
- export { createAppStructure } from "./helpers/createAppStructure";
186
- export type {
187
- AppStructureItem,
188
- AppStructureGroupItem,
189
- AppStructureLeafItem,
190
- AppStructureSubPerm,
191
- AppRoute,
192
- AppFlatMenu,
193
- AppMenu,
194
- AppPerm,
195
- AppFlatPerm,
196
- AppStructure,
197
- } from "./helpers/createAppStructure";
170
+ export * from "./helpers/mergeStyles";
171
+ export * from "./helpers/createAppStructure";
198
172
 
199
173
  //#endregion
@@ -52,6 +52,7 @@ export default {
52
52
  "dropdown": "1000",
53
53
  "modal-backdrop": "1999",
54
54
  "modal": "2000",
55
+ "notification": "3000",
55
56
  },
56
57
  },
57
58
  },