@alepha/ui 0.11.5 → 0.11.7

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/index.js CHANGED
@@ -1,25 +1,316 @@
1
- import { i as ToastService, n as Omnibar_default, r as useToast, t as AlephaMantineProvider_default } from "./AlephaMantineProvider-Be0DAazb.js";
2
- import { $module, TypeBoxError } from "@alepha/core";
3
- import { AlephaReactForm, useFormState } from "@alepha/react-form";
1
+ import { i as ToastService, n as Omnibar_default, r as useToast, t as AlephaMantineProvider_default } from "./AlephaMantineProvider-CzMrw7V3.js";
2
+ import { $module, Alepha, TypeBoxError, t } from "@alepha/core";
3
+ import { AlephaReactForm, useForm, useFormState } from "@alepha/react-form";
4
4
  import { AlephaReactHead } from "@alepha/react-head";
5
5
  import { AlephaReactI18n, useI18n } from "@alepha/react-i18n";
6
6
  import { $page, NestedView, useAction, useActive, useEvents, useInject, useRouter, useStore } from "@alepha/react";
7
+ import { ActionIcon, AppShell, Autocomplete, Badge, Box, Burger, Button, Collapse, ColorInput, CopyButton, Divider, FileInput, Flex, Flex as Flex$1, Grid, Group, Input, Kbd, Menu, MultiSelect, NumberInput, Pagination, PasswordInput, Popover, SegmentedControl, Select, Slider, Stack, Switch, Table, TagsInput, Text, Text as Text$1, TextInput, Textarea, ThemeIcon, Tooltip, useComputedColorScheme, useMantineColorScheme } from "@mantine/core";
7
8
  import { modals } from "@mantine/modals";
8
- import { AppShell, Autocomplete, Burger, Button, ColorInput, Divider, FileInput, Flex, Flex as Flex$1, Grid, Group, Input, Kbd, Menu, MultiSelect, NumberInput, PasswordInput, SegmentedControl, Select, Switch, Table, TagsInput, Text, Text as Text$1, TextInput, Textarea, ThemeIcon, Tooltip, useComputedColorScheme, useMantineColorScheme } from "@mantine/core";
9
- import { Fragment, jsx, jsxs } from "react/jsx-runtime";
9
+ import { IconAt, IconCalendar, IconCheck, IconChevronDown, IconChevronRight, IconClock, IconColorPicker, IconCopy, IconFile, IconFilter, IconHash, IconInfoTriangle, IconKey, IconLanguage, IconLetterCase, IconLink, IconList, IconMail, IconMoon, IconPalette, IconPhone, IconSearch, IconSelector, IconSquareRounded, IconSun, IconToggleLeft, IconX } from "@tabler/icons-react";
10
10
  import { useCallback, useEffect, useRef, useState } from "react";
11
- import { IconAt, IconCalendar, IconCheck, IconChevronDown, IconChevronRight, IconClock, IconColorPicker, IconFile, IconHash, IconKey, IconLanguage, IconLetterCase, IconLink, IconList, IconMail, IconMoon, IconPalette, IconPhone, IconSearch, IconSelector, IconSquareRounded, IconSun, IconToggleLeft } from "@tabler/icons-react";
11
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
12
12
  import { spotlight } from "@mantine/spotlight";
13
13
  import { DateInput, DateTimePicker, TimeInput } from "@mantine/dates";
14
+ import { parseQueryString } from "@alepha/postgres";
15
+ import { DateTimeProvider } from "@alepha/datetime";
16
+ import { useDebouncedCallback } from "@mantine/hooks";
14
17
 
15
18
  //#region src/RootRouter.ts
16
19
  var RootRouter = class {
17
20
  root = $page({
18
21
  path: "/",
19
- lazy: () => import("./AlephaMantineProvider-Ba88lMeq.js")
22
+ lazy: () => import("./AlephaMantineProvider-B4TwQ4tY.js")
20
23
  });
21
24
  };
22
25
 
26
+ //#endregion
27
+ //#region src/components/data/JsonViewer.tsx
28
+ const getSizeConfig = (size = "sm") => {
29
+ const configs = {
30
+ xs: {
31
+ text: "xs",
32
+ icon: 12,
33
+ indent: 16,
34
+ gap: 2
35
+ },
36
+ sm: {
37
+ text: "sm",
38
+ icon: 14,
39
+ indent: 20,
40
+ gap: 4
41
+ },
42
+ md: {
43
+ text: "md",
44
+ icon: 16,
45
+ indent: 24,
46
+ gap: 6
47
+ },
48
+ lg: {
49
+ text: "lg",
50
+ icon: 18,
51
+ indent: 28,
52
+ gap: 8
53
+ },
54
+ xl: {
55
+ text: "xl",
56
+ icon: 20,
57
+ indent: 32,
58
+ gap: 10
59
+ }
60
+ };
61
+ return configs[size] || configs.sm;
62
+ };
63
+ const JsonNode = ({ name, value, depth, maxDepth, isLast = false, isArrayItem = false, size = "sm" }) => {
64
+ const [expanded, setExpanded] = useState(depth < 2);
65
+ const sizeConfig = getSizeConfig(size);
66
+ const getValueType = (val) => {
67
+ if (val === null) return "null";
68
+ if (val === void 0) return "undefined";
69
+ if (Array.isArray(val)) return "array";
70
+ return typeof val;
71
+ };
72
+ const valueType = getValueType(value);
73
+ const renderPrimitive = (val) => {
74
+ switch (getValueType(val)) {
75
+ case "string": return /* @__PURE__ */ jsxs(Text$1, {
76
+ component: "span",
77
+ c: "teal",
78
+ ff: "monospace",
79
+ size: sizeConfig.text,
80
+ style: { whiteSpace: "nowrap" },
81
+ children: [
82
+ "\"",
83
+ val,
84
+ "\""
85
+ ]
86
+ });
87
+ case "number": return /* @__PURE__ */ jsx(Text$1, {
88
+ component: "span",
89
+ c: "blue",
90
+ ff: "monospace",
91
+ size: sizeConfig.text,
92
+ style: { whiteSpace: "nowrap" },
93
+ children: val
94
+ });
95
+ case "boolean": return /* @__PURE__ */ jsx(Text$1, {
96
+ component: "span",
97
+ c: "violet",
98
+ ff: "monospace",
99
+ size: sizeConfig.text,
100
+ style: { whiteSpace: "nowrap" },
101
+ children: String(val)
102
+ });
103
+ case "null": return /* @__PURE__ */ jsx(Text$1, {
104
+ component: "span",
105
+ c: "dimmed",
106
+ ff: "monospace",
107
+ size: sizeConfig.text,
108
+ style: { whiteSpace: "nowrap" },
109
+ children: "null"
110
+ });
111
+ case "undefined": return /* @__PURE__ */ jsx(Text$1, {
112
+ component: "span",
113
+ c: "dimmed",
114
+ ff: "monospace",
115
+ size: sizeConfig.text,
116
+ style: { whiteSpace: "nowrap" },
117
+ children: "undefined"
118
+ });
119
+ default: return /* @__PURE__ */ jsx(Text$1, {
120
+ component: "span",
121
+ ff: "monospace",
122
+ size: sizeConfig.text,
123
+ style: { whiteSpace: "nowrap" },
124
+ children: String(val)
125
+ });
126
+ }
127
+ };
128
+ const renderKey = () => {
129
+ if (!name) return null;
130
+ return /* @__PURE__ */ jsxs(Text$1, {
131
+ component: "span",
132
+ c: "cyan",
133
+ ff: "monospace",
134
+ fw: 500,
135
+ size: sizeConfig.text,
136
+ children: [isArrayItem ? `[${name}]` : `"${name}"`, ":"]
137
+ });
138
+ };
139
+ if (valueType === "object" || valueType === "array") {
140
+ const isObject = valueType === "object";
141
+ const entries = isObject ? Object.entries(value) : value.map((v, i) => [i, v]);
142
+ const isEmpty = entries.length === 0;
143
+ const canExpand = depth < maxDepth && !isEmpty;
144
+ const preview = isObject ? "{...}" : "[...]";
145
+ const brackets = isObject ? ["{", "}"] : ["[", "]"];
146
+ return /* @__PURE__ */ jsxs(Box, { children: [/* @__PURE__ */ jsxs(Box, {
147
+ style: {
148
+ display: "flex",
149
+ alignItems: "center",
150
+ gap: sizeConfig.gap,
151
+ minWidth: "max-content"
152
+ },
153
+ children: [
154
+ canExpand && /* @__PURE__ */ jsx(ActionIcon, {
155
+ size: "xs",
156
+ variant: "transparent",
157
+ c: "dimmed",
158
+ onClick: () => setExpanded(!expanded),
159
+ style: {
160
+ cursor: "pointer",
161
+ flexShrink: 0
162
+ },
163
+ children: expanded ? /* @__PURE__ */ jsx(IconChevronDown, { size: sizeConfig.icon }) : /* @__PURE__ */ jsx(IconChevronRight, { size: sizeConfig.icon })
164
+ }),
165
+ !canExpand && /* @__PURE__ */ jsx(Box, {
166
+ w: sizeConfig.icon + 6,
167
+ style: { flexShrink: 0 }
168
+ }),
169
+ /* @__PURE__ */ jsx(Box, {
170
+ style: { flexShrink: 0 },
171
+ children: renderKey()
172
+ }),
173
+ " ",
174
+ /* @__PURE__ */ jsx(Text$1, {
175
+ component: "span",
176
+ c: "dimmed",
177
+ ff: "monospace",
178
+ size: sizeConfig.text,
179
+ style: { flexShrink: 0 },
180
+ children: brackets[0]
181
+ }),
182
+ !expanded && !isEmpty && /* @__PURE__ */ jsx(Text$1, {
183
+ component: "span",
184
+ c: "dimmed",
185
+ ff: "monospace",
186
+ fs: "italic",
187
+ size: sizeConfig.text,
188
+ style: { flexShrink: 0 },
189
+ children: preview
190
+ }),
191
+ (isEmpty || !expanded) && /* @__PURE__ */ jsx(Text$1, {
192
+ component: "span",
193
+ c: "dimmed",
194
+ ff: "monospace",
195
+ size: sizeConfig.text,
196
+ style: { flexShrink: 0 },
197
+ children: brackets[1]
198
+ }),
199
+ !isEmpty && !expanded && /* @__PURE__ */ jsxs(Text$1, {
200
+ component: "span",
201
+ c: "dimmed",
202
+ size: sizeConfig.text,
203
+ style: { flexShrink: 0 },
204
+ children: [
205
+ entries.length,
206
+ " ",
207
+ entries.length === 1 ? "item" : "items"
208
+ ]
209
+ })
210
+ ]
211
+ }), /* @__PURE__ */ jsxs(Collapse, {
212
+ in: expanded && canExpand,
213
+ children: [/* @__PURE__ */ jsx(Box, {
214
+ pl: sizeConfig.indent,
215
+ style: {
216
+ borderLeft: "1px solid var(--mantine-color-default-border)",
217
+ marginLeft: Math.floor((sizeConfig.icon + 6) / 2)
218
+ },
219
+ children: entries.map(([key, val], index) => /* @__PURE__ */ jsx(JsonNode, {
220
+ name: String(key),
221
+ value: val,
222
+ depth: depth + 1,
223
+ maxDepth,
224
+ isLast: index === entries.length - 1,
225
+ isArrayItem: !isObject,
226
+ size
227
+ }, String(key)))
228
+ }), /* @__PURE__ */ jsxs(Box, {
229
+ style: {
230
+ display: "flex",
231
+ minWidth: "max-content"
232
+ },
233
+ children: [/* @__PURE__ */ jsx(Box, {
234
+ w: sizeConfig.icon + 6,
235
+ style: { flexShrink: 0 }
236
+ }), /* @__PURE__ */ jsx(Text$1, {
237
+ c: "dimmed",
238
+ ff: "monospace",
239
+ size: sizeConfig.text,
240
+ style: { flexShrink: 0 },
241
+ children: brackets[1]
242
+ })]
243
+ })]
244
+ })] });
245
+ }
246
+ return /* @__PURE__ */ jsxs(Box, {
247
+ style: {
248
+ display: "flex",
249
+ alignItems: "center",
250
+ gap: sizeConfig.gap,
251
+ minWidth: "max-content"
252
+ },
253
+ children: [
254
+ /* @__PURE__ */ jsx(Box, {
255
+ w: sizeConfig.icon + 6,
256
+ style: { flexShrink: 0 }
257
+ }),
258
+ /* @__PURE__ */ jsx(Box, {
259
+ style: { flexShrink: 0 },
260
+ children: renderKey()
261
+ }),
262
+ /* @__PURE__ */ jsx(Box, {
263
+ style: { flexShrink: 0 },
264
+ children: renderPrimitive(value)
265
+ }),
266
+ !isLast && /* @__PURE__ */ jsx(Text$1, {
267
+ component: "span",
268
+ c: "dimmed",
269
+ ff: "monospace",
270
+ size: sizeConfig.text,
271
+ style: { flexShrink: 0 },
272
+ children: ","
273
+ })
274
+ ]
275
+ });
276
+ };
277
+ const JsonViewer = ({ data, defaultExpanded = true, maxDepth = 10, copyable = true, size = "sm" }) => {
278
+ const copyIconSize = getSizeConfig(size).icon + 2;
279
+ return /* @__PURE__ */ jsxs(Box, {
280
+ pos: "relative",
281
+ w: "100%",
282
+ children: [copyable && /* @__PURE__ */ jsx(Box, {
283
+ pos: "absolute",
284
+ top: 0,
285
+ right: 0,
286
+ style: { zIndex: 1 },
287
+ children: /* @__PURE__ */ jsx(CopyButton, {
288
+ value: JSON.stringify(data, null, 2),
289
+ children: ({ copied, copy }) => /* @__PURE__ */ jsx(Tooltip, {
290
+ label: copied ? "Copied" : "Copy JSON",
291
+ children: /* @__PURE__ */ jsx(ActionIcon, {
292
+ color: copied ? "teal" : "gray",
293
+ variant: "subtle",
294
+ onClick: copy,
295
+ size,
296
+ children: copied ? /* @__PURE__ */ jsx(IconCheck, { size: copyIconSize }) : /* @__PURE__ */ jsx(IconCopy, { size: copyIconSize })
297
+ })
298
+ })
299
+ })
300
+ }), /* @__PURE__ */ jsx(Box, {
301
+ pt: copyable ? 30 : 0,
302
+ style: { overflowX: "auto" },
303
+ children: /* @__PURE__ */ jsx(JsonNode, {
304
+ value: data,
305
+ depth: 0,
306
+ maxDepth,
307
+ size
308
+ })
309
+ })]
310
+ });
311
+ };
312
+ var JsonViewer_default = JsonViewer;
313
+
23
314
  //#endregion
24
315
  //#region src/components/dialogs/AlertDialog.tsx
25
316
  const AlertDialog = ({ options, onClose }) => /* @__PURE__ */ jsxs(Fragment, { children: [options?.message && /* @__PURE__ */ jsx(Text$1, {
@@ -98,6 +389,16 @@ const PromptDialog = ({ options, onSubmit }) => {
98
389
  };
99
390
  var PromptDialog_default = PromptDialog;
100
391
 
392
+ //#endregion
393
+ //#region src/constants/ui.ts
394
+ const ui = { colors: {
395
+ transparent: "transparent",
396
+ background: "var(--alepha-background)",
397
+ surface: "var(--alepha-surface)",
398
+ elevated: "var(--alepha-elevated)",
399
+ border: "var(--alepha-border)"
400
+ } };
401
+
101
402
  //#endregion
102
403
  //#region src/services/DialogService.tsx
103
404
  var DialogService = class {
@@ -192,7 +493,24 @@ var DialogService = class {
192
493
  /**
193
494
  * Show a JSON editor/viewer dialog
194
495
  */
195
- json(data, options) {}
496
+ json(data, options) {
497
+ this.open({
498
+ size: "lg",
499
+ title: options?.title || "Json Viewer",
500
+ ...options,
501
+ content: /* @__PURE__ */ jsx(Flex$1, {
502
+ bdrs: "md",
503
+ w: "100%",
504
+ flex: 1,
505
+ p: "sm",
506
+ bg: ui.colors.surface,
507
+ children: /* @__PURE__ */ jsx(JsonViewer_default, {
508
+ size: "xs",
509
+ data
510
+ })
511
+ })
512
+ });
513
+ }
196
514
  /**
197
515
  * Show a form dialog for structured input
198
516
  */
@@ -253,7 +571,7 @@ const ActionButton = (_props) => {
253
571
  variant: "subtle",
254
572
  ..._props
255
573
  };
256
- const { tooltip, menu, icon,...restProps } = props;
574
+ const { tooltip, menu, icon, ...restProps } = props;
257
575
  if (props.icon) {
258
576
  const icon$1 = /* @__PURE__ */ jsx(ThemeIcon, {
259
577
  w: 24,
@@ -273,7 +591,7 @@ const ActionButton = (_props) => {
273
591
  restProps.p ??= "xs";
274
592
  }
275
593
  if (props.textVisibleFrom) {
276
- const { children, textVisibleFrom, leftSection,...rest } = restProps;
594
+ const { children, textVisibleFrom, leftSection, ...rest } = restProps;
277
595
  return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(Flex$1, {
278
596
  w: "100%",
279
597
  visibleFrom: textVisibleFrom,
@@ -299,7 +617,7 @@ const ActionButton = (_props) => {
299
617
  }
300
618
  const renderAction = () => {
301
619
  if ("href" in restProps && restProps.href) {
302
- if (restProps.href.startsWith("http")) return /* @__PURE__ */ jsx(ActionHrefButton, {
620
+ if (restProps.href.startsWith("http") || restProps.target) return /* @__PURE__ */ jsx(ActionHrefButton, {
303
621
  ...restProps,
304
622
  href: restProps.href,
305
623
  children: restProps.children
@@ -322,11 +640,18 @@ const ActionButton = (_props) => {
322
640
  onClick: restProps.onClick,
323
641
  children: restProps.children
324
642
  });
325
- if ("form" in restProps && restProps.form) return /* @__PURE__ */ jsx(ActionSubmitButton, {
326
- ...restProps,
327
- form: restProps.form,
328
- children: restProps.children
329
- });
643
+ if ("form" in restProps && restProps.form) {
644
+ if (restProps.type === "reset") return /* @__PURE__ */ jsx(ActionResetButton, {
645
+ ...restProps,
646
+ form: restProps.form,
647
+ children: restProps.children
648
+ });
649
+ return /* @__PURE__ */ jsx(ActionSubmitButton, {
650
+ ...restProps,
651
+ form: restProps.form,
652
+ children: restProps.children
653
+ });
654
+ }
330
655
  return /* @__PURE__ */ jsx(Button, {
331
656
  ...restProps,
332
657
  children: restProps.children
@@ -347,13 +672,18 @@ const ActionButton = (_props) => {
347
672
  index
348
673
  }, index)) })]
349
674
  });
350
- if (tooltip) return /* @__PURE__ */ jsx(Tooltip, { ...typeof tooltip === "string" ? {
351
- label: tooltip,
352
- children: actionElement
353
- } : {
354
- ...tooltip,
355
- children: actionElement
356
- } });
675
+ if (tooltip) {
676
+ const defaultTooltipProps = { openDelay: 1e3 };
677
+ return /* @__PURE__ */ jsx(Tooltip, { ...typeof tooltip === "string" ? {
678
+ ...defaultTooltipProps,
679
+ label: tooltip,
680
+ children: actionElement
681
+ } : {
682
+ ...defaultTooltipProps,
683
+ ...tooltip,
684
+ children: actionElement
685
+ } });
686
+ }
357
687
  return actionElement;
358
688
  };
359
689
  var ActionButton_default = ActionButton;
@@ -361,7 +691,7 @@ var ActionButton_default = ActionButton;
361
691
  * Action button that submits a form with loading and disabled state handling.
362
692
  */
363
693
  const ActionSubmitButton = (props) => {
364
- const { form,...buttonProps } = props;
694
+ const { form, ...buttonProps } = props;
365
695
  const state = useFormState(form);
366
696
  return /* @__PURE__ */ jsx(Button, {
367
697
  ...buttonProps,
@@ -371,6 +701,16 @@ const ActionSubmitButton = (props) => {
371
701
  children: props.children
372
702
  });
373
703
  };
704
+ const ActionResetButton = (props) => {
705
+ const { form, ...buttonProps } = props;
706
+ const state = useFormState(form);
707
+ return /* @__PURE__ */ jsx(Button, {
708
+ ...buttonProps,
709
+ disabled: state.loading,
710
+ type: "reset",
711
+ children: props.children
712
+ });
713
+ };
374
714
  /**
375
715
  * Action button that integrates with useAction hook return value.
376
716
  * Automatically handles loading state and executes the action on click.
@@ -389,7 +729,7 @@ const ActionSubmitButton = (props) => {
389
729
  * ```
390
730
  */
391
731
  const ActionHookButton = (props) => {
392
- const { action,...buttonProps } = props;
732
+ const { action, ...buttonProps } = props;
393
733
  return /* @__PURE__ */ jsx(Button, {
394
734
  ...buttonProps,
395
735
  disabled: action.loading || props.disabled,
@@ -424,7 +764,7 @@ const ActionClickButton = (props) => {
424
764
  * Action for navigation with active state support.
425
765
  */
426
766
  const ActionNavigationButton = (props) => {
427
- const { active: options, classNameActive, variantActive, routerGoOptions,...buttonProps } = props;
767
+ const { active: options, classNameActive, variantActive, routerGoOptions, ...buttonProps } = props;
428
768
  const router = useRouter();
429
769
  const { isPending, isActive } = useActive(options ? {
430
770
  href: props.href,
@@ -443,7 +783,7 @@ const ActionNavigationButton = (props) => {
443
783
  });
444
784
  };
445
785
  const ActionHrefButton = (props) => {
446
- const { active: options, classNameActive, variantActive, routerGoOptions, target,...buttonProps } = props;
786
+ const { active: options, classNameActive, variantActive, routerGoOptions, target, ...buttonProps } = props;
447
787
  return /* @__PURE__ */ jsx(Button, {
448
788
  component: "a",
449
789
  target,
@@ -705,6 +1045,438 @@ const ControlDate = (props) => {
705
1045
  };
706
1046
  var ControlDate_default = ControlDate;
707
1047
 
1048
+ //#endregion
1049
+ //#region src/components/form/ControlNumber.tsx
1050
+ /**
1051
+ *
1052
+ */
1053
+ const ControlNumber = (props) => {
1054
+ const { inputProps, id, icon } = parseInput(props, useFormState(props.input));
1055
+ const ref = useRef(null);
1056
+ const [value, setValue] = useState(props.input.props.defaultValue);
1057
+ useEvents({ "form:reset": (event) => {
1058
+ if (event.id === props.input?.form.id && ref.current) setValue(props.input.props.defaultValue);
1059
+ } }, [props.input]);
1060
+ if (!props.input?.props) return null;
1061
+ const { type, ...inputPropsWithoutType } = props.input.props;
1062
+ if (props.sliderProps) return /* @__PURE__ */ jsx(Input.Wrapper, {
1063
+ ...inputProps,
1064
+ children: /* @__PURE__ */ jsx("div", {
1065
+ style: {
1066
+ height: 32,
1067
+ padding: 8
1068
+ },
1069
+ children: /* @__PURE__ */ jsx(Slider, {
1070
+ ...inputProps,
1071
+ ref,
1072
+ id,
1073
+ ...inputPropsWithoutType,
1074
+ ...props.sliderProps,
1075
+ value,
1076
+ onChange: (val) => {
1077
+ setValue(val);
1078
+ props.input.set(val);
1079
+ }
1080
+ })
1081
+ })
1082
+ });
1083
+ return /* @__PURE__ */ jsx(NumberInput, {
1084
+ ...inputProps,
1085
+ ref,
1086
+ id,
1087
+ leftSection: icon,
1088
+ ...inputPropsWithoutType,
1089
+ ...props.numberInputProps,
1090
+ value: value ?? "",
1091
+ onChange: (val) => {
1092
+ const newValue = val !== null ? Number(val) : void 0;
1093
+ setValue(newValue);
1094
+ props.input.set(newValue);
1095
+ }
1096
+ });
1097
+ };
1098
+ var ControlNumber_default = ControlNumber;
1099
+
1100
+ //#endregion
1101
+ //#region src/utils/extractSchemaFields.ts
1102
+ /**
1103
+ * Extract field information from a TypeBox schema for query building.
1104
+ * Supports nested objects and provides field metadata for autocomplete.
1105
+ */
1106
+ function extractSchemaFields(schema, prefix = "") {
1107
+ const fields = [];
1108
+ if (!schema || typeof schema !== "object") return fields;
1109
+ const properties = "properties" in schema ? schema.properties : schema;
1110
+ if (!properties || typeof properties !== "object") return fields;
1111
+ for (const [key, value] of Object.entries(properties)) {
1112
+ if (typeof value !== "object" || value === null) continue;
1113
+ const fieldSchema = value;
1114
+ const path = prefix ? `${prefix}.${key}` : key;
1115
+ const format = "format" in fieldSchema ? fieldSchema.format : void 0;
1116
+ let displayType = "type" in fieldSchema ? fieldSchema.type : "object";
1117
+ if (format === "date-time") displayType = "datetime";
1118
+ else if (format === "date") displayType = "date";
1119
+ else if (format === "time") displayType = "time";
1120
+ else if (format === "duration") displayType = "duration";
1121
+ const field = {
1122
+ name: key,
1123
+ path,
1124
+ type: displayType,
1125
+ format,
1126
+ description: "description" in fieldSchema ? fieldSchema.description : void 0
1127
+ };
1128
+ if ("enum" in fieldSchema && fieldSchema.enum) {
1129
+ field.enum = fieldSchema.enum;
1130
+ field.type = "enum";
1131
+ }
1132
+ if ("type" in fieldSchema && fieldSchema.type === "object" && "properties" in fieldSchema && typeof fieldSchema.properties === "object") field.nested = extractSchemaFields(fieldSchema.properties, path);
1133
+ fields.push(field);
1134
+ if (field.nested) fields.push(...field.nested);
1135
+ }
1136
+ return fields;
1137
+ }
1138
+ /**
1139
+ * Get suggested operators based on field type
1140
+ */
1141
+ function getOperatorsForField(field) {
1142
+ const allOperators = ["=", "!="];
1143
+ if (field.enum) return [...allOperators, "in"];
1144
+ switch (field.type) {
1145
+ case "string":
1146
+ case "text": return [
1147
+ ...allOperators,
1148
+ "~",
1149
+ "~*",
1150
+ "null"
1151
+ ];
1152
+ case "number":
1153
+ case "integer": return [
1154
+ ...allOperators,
1155
+ ">",
1156
+ ">=",
1157
+ "<",
1158
+ "<="
1159
+ ];
1160
+ case "boolean": return allOperators;
1161
+ case "datetime":
1162
+ case "date": return [
1163
+ ...allOperators,
1164
+ ">",
1165
+ ">=",
1166
+ "<",
1167
+ "<="
1168
+ ];
1169
+ default: return [...allOperators, "null"];
1170
+ }
1171
+ }
1172
+ /**
1173
+ * Get operator symbol and description
1174
+ */
1175
+ const OPERATOR_INFO = {
1176
+ eq: {
1177
+ symbol: "=",
1178
+ label: "equals",
1179
+ example: "name=John"
1180
+ },
1181
+ ne: {
1182
+ symbol: "!=",
1183
+ label: "not equals",
1184
+ example: "status!=archived"
1185
+ },
1186
+ gt: {
1187
+ symbol: ">",
1188
+ label: "greater than",
1189
+ example: "age>18"
1190
+ },
1191
+ gte: {
1192
+ symbol: ">=",
1193
+ label: "greater or equal",
1194
+ example: "age>=18"
1195
+ },
1196
+ lt: {
1197
+ symbol: "<",
1198
+ label: "less than",
1199
+ example: "age<65"
1200
+ },
1201
+ lte: {
1202
+ symbol: "<=",
1203
+ label: "less or equal",
1204
+ example: "age<=65"
1205
+ },
1206
+ like: {
1207
+ symbol: "~",
1208
+ label: "like (case-sensitive)",
1209
+ example: "name~John"
1210
+ },
1211
+ ilike: {
1212
+ symbol: "~*",
1213
+ label: "like (case-insensitive)",
1214
+ example: "name~*john"
1215
+ },
1216
+ null: {
1217
+ symbol: "=null",
1218
+ label: "is null",
1219
+ example: "deletedAt=null"
1220
+ },
1221
+ notNull: {
1222
+ symbol: "!=null",
1223
+ label: "is not null",
1224
+ example: "email!=null"
1225
+ },
1226
+ in: {
1227
+ symbol: "[...]",
1228
+ label: "in array",
1229
+ example: "status=[active,pending]"
1230
+ }
1231
+ };
1232
+
1233
+ //#endregion
1234
+ //#region src/components/form/ControlQueryBuilder.tsx
1235
+ /**
1236
+ * Query builder with text input and help popover.
1237
+ * Generates query strings for parseQueryString syntax.
1238
+ */
1239
+ const ControlQueryBuilder = ({ schema, value = "", onChange, placeholder = "Enter query or click for assistance...", ...textInputProps }) => {
1240
+ const [helpOpened, setHelpOpened] = useState(false);
1241
+ const [textValue, setTextValue] = useState(value);
1242
+ const inputRef = useRef(null);
1243
+ const fields = schema ? extractSchemaFields(schema) : [];
1244
+ const [error, setError] = useState(null);
1245
+ const isValid = (value$1) => {
1246
+ try {
1247
+ parseQueryString(value$1.trim());
1248
+ } catch (e) {
1249
+ setError(e.message);
1250
+ return false;
1251
+ }
1252
+ setError(null);
1253
+ return true;
1254
+ };
1255
+ const handleTextChange = (newValue) => {
1256
+ setTextValue(newValue);
1257
+ if (isValid(newValue)) onChange?.(newValue);
1258
+ };
1259
+ const handleClear = () => {
1260
+ setTextValue("");
1261
+ onChange?.("");
1262
+ isValid("");
1263
+ };
1264
+ const handleInsert = (text) => {
1265
+ const newValue = textValue ? `${textValue}${text} ` : `${text} `;
1266
+ setTextValue(newValue);
1267
+ if (isValid(newValue)) onChange?.(newValue);
1268
+ setTimeout(() => {
1269
+ inputRef.current?.focus();
1270
+ const length = inputRef.current?.value.length || 0;
1271
+ inputRef.current?.setSelectionRange(length, length);
1272
+ }, 0);
1273
+ };
1274
+ useEvents({ "form:change": (event) => {
1275
+ if (event.id === inputRef.current?.form?.id) {
1276
+ if (event.path === textInputProps["data-path"]) setTextValue(event.value ?? "");
1277
+ }
1278
+ } }, []);
1279
+ return /* @__PURE__ */ jsxs(Popover, {
1280
+ width: 800,
1281
+ position: "bottom-start",
1282
+ shadow: "md",
1283
+ opened: helpOpened,
1284
+ onChange: setHelpOpened,
1285
+ closeOnClickOutside: true,
1286
+ closeOnEscape: true,
1287
+ transitionProps: {
1288
+ transition: "fade-up",
1289
+ duration: 200,
1290
+ timingFunction: "ease"
1291
+ },
1292
+ children: [/* @__PURE__ */ jsx(Popover.Target, { children: /* @__PURE__ */ jsx(TextInput, {
1293
+ ref: inputRef,
1294
+ placeholder,
1295
+ value: textValue,
1296
+ onChange: (e) => handleTextChange(e.currentTarget.value),
1297
+ onFocus: () => setHelpOpened(true),
1298
+ leftSection: error ? /* @__PURE__ */ jsx(IconInfoTriangle, { size: 16 }) : /* @__PURE__ */ jsx(IconFilter, { size: 16 }),
1299
+ rightSection: textValue && /* @__PURE__ */ jsx(ActionIcon, {
1300
+ size: "sm",
1301
+ variant: "subtle",
1302
+ color: "gray",
1303
+ onClick: handleClear,
1304
+ children: /* @__PURE__ */ jsx(IconX, { size: 14 })
1305
+ }),
1306
+ ...textInputProps
1307
+ }) }), /* @__PURE__ */ jsx(Popover.Dropdown, {
1308
+ bg: "transparent",
1309
+ p: "xs",
1310
+ bd: `1px solid ${ui.colors.border}`,
1311
+ style: { backdropFilter: "blur(20px)" },
1312
+ children: /* @__PURE__ */ jsx(QueryHelp, {
1313
+ fields,
1314
+ onInsert: handleInsert
1315
+ })
1316
+ })]
1317
+ });
1318
+ };
1319
+ function QueryHelp({ fields, onInsert }) {
1320
+ return /* @__PURE__ */ jsxs(Group, {
1321
+ gap: "md",
1322
+ align: "flex-start",
1323
+ wrap: "nowrap",
1324
+ bg: ui.colors.surface,
1325
+ p: "sm",
1326
+ bdrs: "sm",
1327
+ children: [
1328
+ /* @__PURE__ */ jsxs(Stack, {
1329
+ gap: "md",
1330
+ style: { flex: 1 },
1331
+ children: [
1332
+ /* @__PURE__ */ jsxs(Stack, {
1333
+ gap: "xs",
1334
+ children: [/* @__PURE__ */ jsx(Text$1, {
1335
+ size: "sm",
1336
+ fw: 600,
1337
+ children: "Operators"
1338
+ }), /* @__PURE__ */ jsx(Stack, {
1339
+ gap: 4,
1340
+ children: Object.entries(OPERATOR_INFO).map(([key, info]) => /* @__PURE__ */ jsxs(Group, {
1341
+ gap: "xs",
1342
+ wrap: "nowrap",
1343
+ children: [/* @__PURE__ */ jsx(ActionButton_default, {
1344
+ px: "xs",
1345
+ size: "xs",
1346
+ h: 24,
1347
+ variant: "default",
1348
+ justify: "center",
1349
+ miw: 48,
1350
+ onClick: () => onInsert(info.symbol),
1351
+ children: info.symbol
1352
+ }), /* @__PURE__ */ jsx(Text$1, {
1353
+ size: "xs",
1354
+ c: "dimmed",
1355
+ style: { flex: 1 },
1356
+ children: info.label
1357
+ })]
1358
+ }, key))
1359
+ })]
1360
+ }),
1361
+ /* @__PURE__ */ jsx(Divider, {}),
1362
+ /* @__PURE__ */ jsxs(Stack, {
1363
+ gap: "xs",
1364
+ children: [/* @__PURE__ */ jsx(Text$1, {
1365
+ size: "sm",
1366
+ fw: 600,
1367
+ children: "Logic"
1368
+ }), /* @__PURE__ */ jsxs(Stack, {
1369
+ gap: 4,
1370
+ children: [/* @__PURE__ */ jsxs(Group, {
1371
+ gap: "xs",
1372
+ wrap: "nowrap",
1373
+ children: [/* @__PURE__ */ jsx(ActionButton_default, {
1374
+ px: "xs",
1375
+ size: "xs",
1376
+ h: 24,
1377
+ variant: "default",
1378
+ justify: "center",
1379
+ miw: 48,
1380
+ onClick: () => onInsert("&"),
1381
+ children: "&"
1382
+ }), /* @__PURE__ */ jsx(Text$1, {
1383
+ size: "xs",
1384
+ c: "dimmed",
1385
+ children: "AND"
1386
+ })]
1387
+ }), /* @__PURE__ */ jsxs(Group, {
1388
+ gap: "xs",
1389
+ wrap: "nowrap",
1390
+ children: [/* @__PURE__ */ jsx(ActionButton_default, {
1391
+ px: "xs",
1392
+ size: "xs",
1393
+ h: 24,
1394
+ variant: "default",
1395
+ justify: "center",
1396
+ miw: 48,
1397
+ onClick: () => onInsert("|"),
1398
+ children: "|"
1399
+ }), /* @__PURE__ */ jsx(Text$1, {
1400
+ size: "xs",
1401
+ c: "dimmed",
1402
+ children: "OR"
1403
+ })]
1404
+ })]
1405
+ })]
1406
+ })
1407
+ ]
1408
+ }),
1409
+ fields.length > 0 && /* @__PURE__ */ jsx(Divider, { orientation: "vertical" }),
1410
+ fields.length > 0 && /* @__PURE__ */ jsxs(Flex$1, {
1411
+ direction: "column",
1412
+ gap: "xs",
1413
+ style: { flex: 2 },
1414
+ children: [/* @__PURE__ */ jsx(Text$1, {
1415
+ size: "sm",
1416
+ fw: 600,
1417
+ children: "Fields"
1418
+ }), /* @__PURE__ */ jsx(Flex$1, {
1419
+ direction: "column",
1420
+ gap: 4,
1421
+ style: {
1422
+ maxHeight: 300,
1423
+ overflowY: "auto"
1424
+ },
1425
+ children: fields.map((field) => /* @__PURE__ */ jsxs(Flex$1, {
1426
+ gap: "xs",
1427
+ wrap: "nowrap",
1428
+ align: "flex-start",
1429
+ children: [
1430
+ /* @__PURE__ */ jsx(ActionButton_default, {
1431
+ px: "xs",
1432
+ size: "xs",
1433
+ h: 24,
1434
+ variant: "default",
1435
+ justify: "end",
1436
+ miw: 120,
1437
+ onClick: () => onInsert(field.path),
1438
+ children: field.path
1439
+ }),
1440
+ /* @__PURE__ */ jsxs(Flex$1, {
1441
+ mt: 3,
1442
+ direction: "column",
1443
+ gap: 2,
1444
+ style: {
1445
+ flex: 1,
1446
+ minWidth: 0
1447
+ },
1448
+ children: [/* @__PURE__ */ jsx(Text$1, {
1449
+ size: "xs",
1450
+ c: "dimmed",
1451
+ lineClamp: 1,
1452
+ children: field.description || field.type
1453
+ }), field.enum && /* @__PURE__ */ jsx(Group, {
1454
+ gap: 0,
1455
+ wrap: "wrap",
1456
+ children: field.enum.map((enumValue) => /* @__PURE__ */ jsx(ActionButton_default, {
1457
+ px: "xs",
1458
+ size: "xs",
1459
+ h: 24,
1460
+ onClick: () => onInsert(enumValue),
1461
+ children: enumValue
1462
+ }, enumValue))
1463
+ })]
1464
+ }),
1465
+ /* @__PURE__ */ jsx(Badge, {
1466
+ size: "xs",
1467
+ variant: "light",
1468
+ style: { flexShrink: 0 },
1469
+ children: field.type
1470
+ })
1471
+ ]
1472
+ }, field.path))
1473
+ })]
1474
+ })
1475
+ ]
1476
+ });
1477
+ }
1478
+ var ControlQueryBuilder_default = ControlQueryBuilder;
1479
+
708
1480
  //#endregion
709
1481
  //#region src/components/form/ControlSelect.tsx
710
1482
  /**
@@ -740,18 +1512,15 @@ const ControlSelect = (props) => {
740
1512
  const segmentedControlProps = typeof props.segmented === "object" ? props.segmented : {};
741
1513
  return /* @__PURE__ */ jsx(Input.Wrapper, {
742
1514
  ...inputProps,
743
- children: /* @__PURE__ */ jsx(Flex$1, {
744
- mt: "calc(var(--mantine-spacing-xs) / 2)",
745
- children: /* @__PURE__ */ jsx(SegmentedControl, {
746
- disabled: inputProps.disabled,
747
- defaultValue: String(props.input.props.defaultValue),
748
- ...segmentedControlProps,
749
- onChange: (value) => {
750
- props.input.set(value);
751
- },
752
- data: data.slice(0, 10)
753
- })
754
- })
1515
+ children: /* @__PURE__ */ jsx(Flex$1, { children: /* @__PURE__ */ jsx(SegmentedControl, {
1516
+ disabled: inputProps.disabled,
1517
+ defaultValue: String(props.input.props.defaultValue),
1518
+ ...segmentedControlProps,
1519
+ onChange: (value) => {
1520
+ props.input.set(value);
1521
+ },
1522
+ data: data.slice(0, 10)
1523
+ }) })
755
1524
  });
756
1525
  }
757
1526
  if (props.autocomplete) {
@@ -827,6 +1596,7 @@ var ControlSelect_default = ControlSelect;
827
1596
  * - DateInput (for date format)
828
1597
  * - DateTimePicker (for date-time format)
829
1598
  * - TimeInput (for time format)
1599
+ * - QueryBuilder (for building type-safe queries with autocomplete)
830
1600
  * - Custom component
831
1601
  *
832
1602
  * Automatically handles labels, descriptions, error messages, required state, and default icons.
@@ -838,6 +1608,15 @@ const Control = (_props) => {
838
1608
  ..._props,
839
1609
  ...schema.$control
840
1610
  };
1611
+ if (props.query) return /* @__PURE__ */ jsx(ControlQueryBuilder_default, {
1612
+ ...props.input.props,
1613
+ ...inputProps,
1614
+ schema: props.query,
1615
+ value: props.input.props.value,
1616
+ onChange: (value) => {
1617
+ props.input.set(value);
1618
+ }
1619
+ });
841
1620
  if (props.custom) {
842
1621
  const Custom = props.custom;
843
1622
  return /* @__PURE__ */ jsx(Input.Wrapper, {
@@ -855,14 +1634,13 @@ const Control = (_props) => {
855
1634
  });
856
1635
  }
857
1636
  if (props.number || props.input.schema && "type" in props.input.schema && (props.input.schema.type === "number" || props.input.schema.type === "integer")) {
858
- const numberInputProps = typeof props.number === "object" ? props.number : {};
859
- const { type,...inputPropsWithoutType } = props.input.props;
860
- return /* @__PURE__ */ jsx(NumberInput, {
861
- ...inputProps,
862
- id,
863
- leftSection: icon,
864
- ...inputPropsWithoutType,
865
- ...numberInputProps
1637
+ const controlNumberProps = typeof props.number === "object" ? props.number : {};
1638
+ return /* @__PURE__ */ jsx(ControlNumber_default, {
1639
+ input: props.input,
1640
+ title: props.title,
1641
+ description: props.description,
1642
+ icon,
1643
+ ...controlNumberProps
866
1644
  });
867
1645
  }
868
1646
  if (props.file) {
@@ -990,15 +1768,16 @@ var Control_default = Control;
990
1768
  */
991
1769
  const TypeForm = (props) => {
992
1770
  const { form, columns = 3, children, controlProps, skipFormElement = false, skipSubmitButton = false, submitButtonProps } = props;
993
- if (!form.options?.schema?.properties) return null;
994
- const supportedFields = Object.keys(form.options.schema.properties).filter((fieldName) => {
1771
+ const schema = props.schema || form.options.schema;
1772
+ if (!schema?.properties) return null;
1773
+ const supportedFields = Object.keys(schema.properties).filter((fieldName) => {
995
1774
  const field = form.input[fieldName];
996
1775
  if (!field || typeof field !== "object" || !("schema" in field)) return false;
997
- const schema = field.schema;
998
- if ("type" in schema) {
999
- if (schema.type === "object") return false;
1776
+ const schema$1 = field.schema;
1777
+ if ("type" in schema$1) {
1778
+ if (schema$1.type === "object") return false;
1000
1779
  }
1001
- if ("properties" in schema && schema.properties) return false;
1780
+ if ("properties" in schema$1 && schema$1.properties) return false;
1002
1781
  return true;
1003
1782
  });
1004
1783
  const colSpan = typeof columns === "number" ? {
@@ -1030,14 +1809,17 @@ const TypeForm = (props) => {
1030
1809
  const content = /* @__PURE__ */ jsxs(Flex$1, {
1031
1810
  direction: "column",
1032
1811
  gap: "sm",
1033
- children: [renderFields(), !skipSubmitButton && /* @__PURE__ */ jsxs(Flex$1, { children: [/* @__PURE__ */ jsx(ActionButton_default, {
1034
- form,
1035
- ...submitButtonProps,
1036
- children: submitButtonProps?.children ?? "Submit"
1037
- }), /* @__PURE__ */ jsx("button", {
1038
- type: "reset",
1039
- children: "Reset"
1040
- })] })]
1812
+ children: [renderFields(), !skipSubmitButton && /* @__PURE__ */ jsxs(Flex$1, {
1813
+ gap: "sm",
1814
+ children: [/* @__PURE__ */ jsx(ActionButton_default, {
1815
+ form,
1816
+ ...submitButtonProps,
1817
+ children: submitButtonProps?.children ?? "Submit"
1818
+ }), /* @__PURE__ */ jsx(ActionButton_default, {
1819
+ type: "reset",
1820
+ children: "Reset"
1821
+ })]
1822
+ })]
1041
1823
  });
1042
1824
  if (skipFormElement) return content;
1043
1825
  return /* @__PURE__ */ jsx("form", {
@@ -1047,15 +1829,6 @@ const TypeForm = (props) => {
1047
1829
  };
1048
1830
  var TypeForm_default = TypeForm;
1049
1831
 
1050
- //#endregion
1051
- //#region src/constants/ui.ts
1052
- const ui = { colors: {
1053
- transparent: "transparent",
1054
- background: "var(--alepha-background)",
1055
- surface: "var(--alepha-surface)",
1056
- elevated: "var(--alepha-elevated)"
1057
- } };
1058
-
1059
1832
  //#endregion
1060
1833
  //#region src/components/buttons/BurgerButton.tsx
1061
1834
  const BurgerButton = (props) => {
@@ -1159,16 +1932,24 @@ const Sidebar = (props) => {
1159
1932
  if (item.type === "search") return /* @__PURE__ */ jsx(OmnibarButton_default, { collapsed: props.collapsed }, key);
1160
1933
  if (item.type === "section") {
1161
1934
  if (props.collapsed) return;
1162
- return /* @__PURE__ */ jsx(Text$1, {
1163
- size: "xs",
1164
- c: "dimmed",
1935
+ return /* @__PURE__ */ jsxs(Flex$1, {
1165
1936
  mt: "md",
1166
1937
  mb: "xs",
1167
- mx: "sm",
1168
- tt: "uppercase",
1169
- fw: "bold",
1170
- children: item.label
1171
- }, key);
1938
+ align: "center",
1939
+ gap: "xs",
1940
+ children: [/* @__PURE__ */ jsx(ThemeIcon, {
1941
+ c: "dimmed",
1942
+ size: "xs",
1943
+ variant: "transparent",
1944
+ children: item.icon
1945
+ }), /* @__PURE__ */ jsx(Text$1, {
1946
+ size: "xs",
1947
+ c: "dimmed",
1948
+ tt: "uppercase",
1949
+ fw: "bold",
1950
+ children: item.label
1951
+ }, key)]
1952
+ });
1172
1953
  }
1173
1954
  }
1174
1955
  if ("element" in item) return /* @__PURE__ */ jsx(Flex$1, { children: item.element }, key);
@@ -1243,7 +2024,7 @@ const SidebarItem = (props) => {
1243
2024
  } }, []);
1244
2025
  if (level > maxLevel) return null;
1245
2026
  const handleItemClick = (e) => {
1246
- e.preventDefault();
2027
+ if (!props.item.target) e.preventDefault();
1247
2028
  if (item.children && item.children.length > 0) setIsOpen(!isOpen);
1248
2029
  else {
1249
2030
  props.onItemClick?.(item);
@@ -1258,6 +2039,7 @@ const SidebarItem = (props) => {
1258
2039
  w: "100%",
1259
2040
  justify: "space-between",
1260
2041
  href: props.item.href,
2042
+ target: props.item.target,
1261
2043
  variant: "subtle",
1262
2044
  size: props.item.theme?.size ?? props.theme.button?.size ?? (level === 0 ? "sm" : "xs"),
1263
2045
  variantActive: "default",
@@ -1316,7 +2098,7 @@ const SidebarCollapsedItem = (props) => {
1316
2098
  }, []);
1317
2099
  const [isOpen, setIsOpen] = useState(isActive(item));
1318
2100
  const handleItemClick = (e) => {
1319
- e.preventDefault();
2101
+ if (!props.item.target) e.preventDefault();
1320
2102
  if (item.children && item.children.length > 0) setIsOpen(!isOpen);
1321
2103
  else {
1322
2104
  props.onItemClick?.(item);
@@ -1331,6 +2113,7 @@ const SidebarCollapsedItem = (props) => {
1331
2113
  onClick: handleItemClick,
1332
2114
  icon: item.icon ?? /* @__PURE__ */ jsx(IconSquareRounded, {}),
1333
2115
  href: props.item.href,
2116
+ target: props.item.target,
1334
2117
  menu: item.children ? {
1335
2118
  position: "right",
1336
2119
  on: "hover",
@@ -1401,27 +2184,176 @@ var AdminShell_default = AdminShell;
1401
2184
  //#endregion
1402
2185
  //#region src/components/table/DataTable.tsx
1403
2186
  const DataTable = (props) => {
1404
- const [items, setItems] = useState(typeof props.items === "function" ? [] : props.items);
2187
+ const [items, setItems] = useState(typeof props.items === "function" ? { content: [] } : props.items);
2188
+ const defaultSize = props.infinityScroll ? 100 : props.defaultSize || 10;
2189
+ const [page, setPage] = useState(1);
2190
+ const [size, setSize] = useState(String(defaultSize));
2191
+ const [currentPage, setCurrentPage] = useState(0);
2192
+ const alepha = useInject(Alepha);
2193
+ const form = useForm({
2194
+ schema: t.object({
2195
+ ...props.filters ? props.filters.properties : {},
2196
+ page: t.number({ default: 0 }),
2197
+ size: t.number({ default: defaultSize }),
2198
+ sort: t.optional(t.string())
2199
+ }),
2200
+ handler: async (values, args) => {
2201
+ if (typeof props.items === "function") {
2202
+ const response = await props.items(values, { items: items.content });
2203
+ if (props.infinityScroll && values.page > 0) setItems((prev) => ({
2204
+ ...response,
2205
+ content: [...prev.content, ...response.content]
2206
+ }));
2207
+ else setItems(response);
2208
+ setCurrentPage(values.page);
2209
+ }
2210
+ },
2211
+ onReset: async () => {
2212
+ setPage(1);
2213
+ setSize("10");
2214
+ await form.submit();
2215
+ },
2216
+ onChange: async (key, value) => {
2217
+ if (key === "page") {
2218
+ setPage(value + 1);
2219
+ await form.submit();
2220
+ return;
2221
+ }
2222
+ if (key === "size") {
2223
+ setSize(String(value));
2224
+ form.input.page.set(0);
2225
+ return;
2226
+ }
2227
+ props.onFilterChange?.(key, value, form);
2228
+ }
2229
+ }, [items]);
2230
+ useDebouncedCallback(() => form.submit(), { delay: 800 });
2231
+ const dt = useInject(DateTimeProvider);
2232
+ useEffect(() => {
2233
+ if (props.submitOnInit) form.submit();
2234
+ if (props.submitEvery) {
2235
+ const it = dt.createInterval(() => {
2236
+ form.submit();
2237
+ }, props.submitEvery);
2238
+ return () => dt.clearInterval(it);
2239
+ }
2240
+ }, []);
1405
2241
  useEffect(() => {
1406
2242
  if (typeof props.items !== "function") setItems(props.items);
1407
2243
  }, [props.items]);
1408
- const head = Object.entries(props.columns).map(([key, col]) => /* @__PURE__ */ jsx(Table.Th, { children: /* @__PURE__ */ jsx(ActionButton_default, {
1409
- justify: "space-between",
1410
- radius: 0,
1411
- fullWidth: true,
1412
- size: "xs",
1413
- children: col.label
1414
- }) }, key));
1415
- const rows = items.map((item, index) => {
2244
+ useEffect(() => {
2245
+ if (!props.infinityScroll || typeof props.items !== "function") return;
2246
+ const handleScroll = () => {
2247
+ if (form.submitting) return;
2248
+ const scrollTop = window.scrollY;
2249
+ const windowHeight = window.innerHeight;
2250
+ const docHeight = document.documentElement.scrollHeight;
2251
+ if (scrollTop + windowHeight >= docHeight - 300) {
2252
+ const totalPages = items.page?.totalPages ?? 1;
2253
+ if (currentPage + 1 < totalPages) form.input.page.set(currentPage + 1);
2254
+ }
2255
+ };
2256
+ window.addEventListener("scroll", handleScroll);
2257
+ return () => window.removeEventListener("scroll", handleScroll);
2258
+ }, [
2259
+ props.infinityScroll,
2260
+ form.submitting,
2261
+ items.page?.totalPages,
2262
+ currentPage,
2263
+ form
2264
+ ]);
2265
+ const head = Object.entries(props.columns).map(([key, col]) => /* @__PURE__ */ jsx(Table.Th, {
2266
+ style: { ...col.fit ? {
2267
+ width: "1%",
2268
+ whiteSpace: "nowrap"
2269
+ } : {} },
2270
+ children: /* @__PURE__ */ jsx(ActionButton_default, {
2271
+ justify: "space-between",
2272
+ radius: 0,
2273
+ fullWidth: true,
2274
+ size: "xs",
2275
+ children: col.label
2276
+ })
2277
+ }, key));
2278
+ const rows = items.content.map((item, index) => {
1416
2279
  const trProps = props.tableTrProps ? props.tableTrProps(item) : {};
1417
2280
  return /* @__PURE__ */ jsx(Table.Tr, {
1418
2281
  ...trProps,
1419
- children: Object.entries(props.columns).map(([key, col]) => /* @__PURE__ */ jsx(Table.Td, { children: col.value(item) }, key))
2282
+ children: Object.entries(props.columns).map(([key, col]) => /* @__PURE__ */ jsx(Table.Td, { children: col.value(item, {
2283
+ index,
2284
+ form,
2285
+ alepha
2286
+ }) }, key))
1420
2287
  }, JSON.stringify(item));
1421
2288
  });
1422
- return /* @__PURE__ */ jsxs(Table, {
1423
- ...props.tableProps,
1424
- children: [/* @__PURE__ */ jsx(Table.Thead, { children: /* @__PURE__ */ jsx(Table.Tr, { children: head }) }), /* @__PURE__ */ jsx(Table.Tbody, { children: rows })]
2289
+ const schema = t.omit(form.options.schema, [
2290
+ "page",
2291
+ "size",
2292
+ "sort"
2293
+ ]);
2294
+ return /* @__PURE__ */ jsxs(Flex$1, {
2295
+ direction: "column",
2296
+ gap: "sm",
2297
+ flex: 1,
2298
+ children: [
2299
+ props.filters ? /* @__PURE__ */ jsx(TypeForm_default, {
2300
+ ...props.typeFormProps,
2301
+ form,
2302
+ schema
2303
+ }) : null,
2304
+ /* @__PURE__ */ jsx(Flex$1, {
2305
+ flex: 1,
2306
+ className: "overflow-auto",
2307
+ children: /* @__PURE__ */ jsxs(Table, {
2308
+ striped: true,
2309
+ withRowBorders: true,
2310
+ withColumnBorders: true,
2311
+ withTableBorder: true,
2312
+ stripedColor: "",
2313
+ ...props.tableProps,
2314
+ children: [/* @__PURE__ */ jsx(Table.Thead, { children: /* @__PURE__ */ jsx(Table.Tr, { children: head }) }), /* @__PURE__ */ jsx(Table.Tbody, { children: rows })]
2315
+ })
2316
+ }),
2317
+ !props.infinityScroll && /* @__PURE__ */ jsxs(Flex$1, {
2318
+ justify: "space-between",
2319
+ align: "center",
2320
+ children: [/* @__PURE__ */ jsx(Pagination, {
2321
+ withEdges: true,
2322
+ total: items.page?.totalPages ?? 1,
2323
+ value: page,
2324
+ onChange: (value) => {
2325
+ form.input.page.set(value - 1);
2326
+ }
2327
+ }), /* @__PURE__ */ jsx(Flex$1, { children: /* @__PURE__ */ jsx(Select, {
2328
+ value: size,
2329
+ onChange: (value) => {
2330
+ form.input.size.set(Number(value));
2331
+ },
2332
+ data: [
2333
+ {
2334
+ value: "5",
2335
+ label: "5"
2336
+ },
2337
+ {
2338
+ value: "10",
2339
+ label: "10"
2340
+ },
2341
+ {
2342
+ value: "25",
2343
+ label: "25"
2344
+ },
2345
+ {
2346
+ value: "50",
2347
+ label: "50"
2348
+ },
2349
+ {
2350
+ value: "100",
2351
+ label: "100"
2352
+ }
2353
+ ]
2354
+ }) })]
2355
+ })
2356
+ ]
1425
2357
  });
1426
2358
  };
1427
2359
  var DataTable_default = DataTable;
@@ -1465,5 +2397,5 @@ const AlephaUI = $module({
1465
2397
  });
1466
2398
 
1467
2399
  //#endregion
1468
- export { ActionButton_default as ActionButton, AdminShell_default as AdminShell, AlephaMantineProvider_default as AlephaMantineProvider, AlephaUI, AlertDialog_default as AlertDialog, AppBar_default as AppBar, ConfirmDialog_default as ConfirmDialog, Control_default as Control, ControlDate_default as ControlDate, ControlSelect_default as ControlSelect, DarkModeButton_default as DarkModeButton, DataTable_default as DataTable, DialogService, Flex, ICON_SIZES, Omnibar_default as Omnibar, OmnibarButton_default as OmnibarButton, PromptDialog_default as PromptDialog, RootRouter, Sidebar, Text, ToastService, TypeForm_default as TypeForm, capitalize, getDefaultIcon, prettyName, ui, useDialog, useToast };
2400
+ export { ActionButton_default as ActionButton, AdminShell_default as AdminShell, AlephaMantineProvider_default as AlephaMantineProvider, AlephaUI, AlertDialog_default as AlertDialog, AppBar_default as AppBar, ConfirmDialog_default as ConfirmDialog, Control_default as Control, ControlDate_default as ControlDate, ControlQueryBuilder_default as ControlQueryBuilder, ControlSelect_default as ControlSelect, DarkModeButton_default as DarkModeButton, DataTable_default as DataTable, DialogService, Flex, ICON_SIZES, JsonViewer_default as JsonViewer, OPERATOR_INFO, Omnibar_default as Omnibar, OmnibarButton_default as OmnibarButton, PromptDialog_default as PromptDialog, RootRouter, Sidebar, Text, ToastService, TypeForm_default as TypeForm, capitalize, extractSchemaFields, getDefaultIcon, getOperatorsForField, prettyName, ui, useDialog, useToast };
1469
2401
  //# sourceMappingURL=index.js.map