@alepha/ui 0.11.4 → 0.11.6

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.
@@ -1,22 +1,24 @@
1
- import { i as ToastService, n as Omnibar_default, r as useToast, t as AlephaMantineProvider_default } from "./AlephaMantineProvider-CtIV-8MV.mjs";
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-Be0DAazb.js";
2
+ import { $module, 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
7
  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";
8
+ import { ActionIcon, AppShell, Autocomplete, Badge, Burger, Button, Code, ColorInput, Divider, FileInput, Flex, Flex as Flex$1, Grid, Group, Input, Kbd, Menu, MultiSelect, NumberInput, Pagination, Paper, PasswordInput, Popover, SegmentedControl, Select, Slider, Stack, Switch, Table, TagsInput, Text, Text as Text$1, TextInput, Textarea, ThemeIcon, Tooltip, useComputedColorScheme, useMantineColorScheme } from "@mantine/core";
9
9
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
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 { IconAt, IconCalendar, IconCheck, IconChevronDown, IconChevronRight, IconClock, IconColorPicker, IconFile, IconFilter, IconHash, IconKey, IconLanguage, IconLetterCase, IconLink, IconList, IconMail, IconMoon, IconPalette, IconPhone, IconSearch, IconSelector, IconSquareRounded, IconSun, IconToggleLeft, IconX } from "@tabler/icons-react";
12
12
  import { spotlight } from "@mantine/spotlight";
13
13
  import { DateInput, DateTimePicker, TimeInput } from "@mantine/dates";
14
+ import { DateTimeProvider } from "@alepha/datetime";
15
+ import { useDebouncedCallback } from "@mantine/hooks";
14
16
 
15
17
  //#region src/RootRouter.ts
16
18
  var RootRouter = class {
17
19
  root = $page({
18
20
  path: "/",
19
- lazy: () => import("./AlephaMantineProvider-D-vu9aCD.mjs")
21
+ lazy: () => import("./AlephaMantineProvider-Ba88lMeq.js")
20
22
  });
21
23
  };
22
24
 
@@ -299,7 +301,7 @@ const ActionButton = (_props) => {
299
301
  }
300
302
  const renderAction = () => {
301
303
  if ("href" in restProps && restProps.href) {
302
- if (restProps.href.startsWith("http")) return /* @__PURE__ */ jsx(ActionHrefButton, {
304
+ if (restProps.href.startsWith("http") || restProps.target) return /* @__PURE__ */ jsx(ActionHrefButton, {
303
305
  ...restProps,
304
306
  href: restProps.href,
305
307
  children: restProps.children
@@ -322,11 +324,18 @@ const ActionButton = (_props) => {
322
324
  onClick: restProps.onClick,
323
325
  children: restProps.children
324
326
  });
325
- if ("form" in restProps && restProps.form) return /* @__PURE__ */ jsx(ActionSubmitButton, {
326
- ...restProps,
327
- form: restProps.form,
328
- children: restProps.children
329
- });
327
+ if ("form" in restProps && restProps.form) {
328
+ if (restProps.type === "reset") return /* @__PURE__ */ jsx(ActionResetButton, {
329
+ ...restProps,
330
+ form: restProps.form,
331
+ children: restProps.children
332
+ });
333
+ return /* @__PURE__ */ jsx(ActionSubmitButton, {
334
+ ...restProps,
335
+ form: restProps.form,
336
+ children: restProps.children
337
+ });
338
+ }
330
339
  return /* @__PURE__ */ jsx(Button, {
331
340
  ...restProps,
332
341
  children: restProps.children
@@ -347,13 +356,18 @@ const ActionButton = (_props) => {
347
356
  index
348
357
  }, index)) })]
349
358
  });
350
- if (tooltip) return /* @__PURE__ */ jsx(Tooltip, { ...typeof tooltip === "string" ? {
351
- label: tooltip,
352
- children: actionElement
353
- } : {
354
- ...tooltip,
355
- children: actionElement
356
- } });
359
+ if (tooltip) {
360
+ const defaultTooltipProps = { openDelay: 1e3 };
361
+ return /* @__PURE__ */ jsx(Tooltip, { ...typeof tooltip === "string" ? {
362
+ ...defaultTooltipProps,
363
+ label: tooltip,
364
+ children: actionElement
365
+ } : {
366
+ ...defaultTooltipProps,
367
+ ...tooltip,
368
+ children: actionElement
369
+ } });
370
+ }
357
371
  return actionElement;
358
372
  };
359
373
  var ActionButton_default = ActionButton;
@@ -371,6 +385,16 @@ const ActionSubmitButton = (props) => {
371
385
  children: props.children
372
386
  });
373
387
  };
388
+ const ActionResetButton = (props) => {
389
+ const { form,...buttonProps } = props;
390
+ const state = useFormState(form);
391
+ return /* @__PURE__ */ jsx(Button, {
392
+ ...buttonProps,
393
+ disabled: state.loading,
394
+ type: "reset",
395
+ children: props.children
396
+ });
397
+ };
374
398
  /**
375
399
  * Action button that integrates with useAction hook return value.
376
400
  * Automatically handles loading state and executes the action on click.
@@ -705,6 +729,403 @@ const ControlDate = (props) => {
705
729
  };
706
730
  var ControlDate_default = ControlDate;
707
731
 
732
+ //#endregion
733
+ //#region src/components/form/ControlNumber.tsx
734
+ /**
735
+ *
736
+ */
737
+ const ControlNumber = (props) => {
738
+ const { inputProps, id, icon } = parseInput(props, useFormState(props.input));
739
+ const ref = useRef(null);
740
+ const [value, setValue] = useState(props.input.props.defaultValue);
741
+ useEvents({ "form:reset": (event) => {
742
+ if (event.id === props.input?.form.id && ref.current) setValue(props.input.props.defaultValue);
743
+ } }, [props.input]);
744
+ if (!props.input?.props) return null;
745
+ const { type,...inputPropsWithoutType } = props.input.props;
746
+ if (props.sliderProps) return /* @__PURE__ */ jsx(Input.Wrapper, {
747
+ ...inputProps,
748
+ children: /* @__PURE__ */ jsx("div", {
749
+ style: {
750
+ height: 32,
751
+ padding: 8
752
+ },
753
+ children: /* @__PURE__ */ jsx(Slider, {
754
+ ...inputProps,
755
+ ref,
756
+ id,
757
+ ...inputPropsWithoutType,
758
+ ...props.sliderProps,
759
+ value,
760
+ onChange: (val) => {
761
+ setValue(val);
762
+ props.input.set(val);
763
+ }
764
+ })
765
+ })
766
+ });
767
+ return /* @__PURE__ */ jsx(NumberInput, {
768
+ ...inputProps,
769
+ ref,
770
+ id,
771
+ leftSection: icon,
772
+ ...inputPropsWithoutType,
773
+ ...props.numberInputProps,
774
+ value: value ?? "",
775
+ onChange: (val) => {
776
+ const newValue = val !== null ? Number(val) : void 0;
777
+ setValue(newValue);
778
+ props.input.set(newValue);
779
+ }
780
+ });
781
+ };
782
+ var ControlNumber_default = ControlNumber;
783
+
784
+ //#endregion
785
+ //#region src/utils/extractSchemaFields.ts
786
+ /**
787
+ * Extract field information from a TypeBox schema for query building.
788
+ * Supports nested objects and provides field metadata for autocomplete.
789
+ */
790
+ function extractSchemaFields(schema, prefix = "") {
791
+ const fields = [];
792
+ if (!schema || typeof schema !== "object") return fields;
793
+ const properties = "properties" in schema ? schema.properties : schema;
794
+ if (!properties || typeof properties !== "object") return fields;
795
+ for (const [key, value] of Object.entries(properties)) {
796
+ if (typeof value !== "object" || value === null) continue;
797
+ const fieldSchema = value;
798
+ const path = prefix ? `${prefix}.${key}` : key;
799
+ const format = "format" in fieldSchema ? fieldSchema.format : void 0;
800
+ let displayType = "type" in fieldSchema ? fieldSchema.type : "unknown";
801
+ if (format === "date-time") displayType = "datetime";
802
+ else if (format === "date") displayType = "date";
803
+ else if (format === "time") displayType = "time";
804
+ else if (format === "duration") displayType = "duration";
805
+ const field = {
806
+ name: key,
807
+ path,
808
+ type: displayType,
809
+ format,
810
+ description: "description" in fieldSchema ? fieldSchema.description : void 0
811
+ };
812
+ if ("enum" in fieldSchema && fieldSchema.enum) field.enum = fieldSchema.enum;
813
+ if ("type" in fieldSchema && fieldSchema.type === "object" && "properties" in fieldSchema && typeof fieldSchema.properties === "object") field.nested = extractSchemaFields(fieldSchema.properties, path);
814
+ fields.push(field);
815
+ if (field.nested) fields.push(...field.nested);
816
+ }
817
+ return fields;
818
+ }
819
+ /**
820
+ * Get suggested operators based on field type
821
+ */
822
+ function getOperatorsForField(field) {
823
+ const allOperators = ["=", "!="];
824
+ if (field.enum) return [...allOperators, "in"];
825
+ switch (field.type) {
826
+ case "string":
827
+ case "text": return [
828
+ ...allOperators,
829
+ "~",
830
+ "~*",
831
+ "null"
832
+ ];
833
+ case "number":
834
+ case "integer": return [
835
+ ...allOperators,
836
+ ">",
837
+ ">=",
838
+ "<",
839
+ "<="
840
+ ];
841
+ case "boolean": return allOperators;
842
+ case "datetime":
843
+ case "date": return [
844
+ ...allOperators,
845
+ ">",
846
+ ">=",
847
+ "<",
848
+ "<="
849
+ ];
850
+ default: return [...allOperators, "null"];
851
+ }
852
+ }
853
+ /**
854
+ * Get operator symbol and description
855
+ */
856
+ const OPERATOR_INFO = {
857
+ eq: {
858
+ symbol: "=",
859
+ label: "equals",
860
+ example: "name=John"
861
+ },
862
+ ne: {
863
+ symbol: "!=",
864
+ label: "not equals",
865
+ example: "status!=archived"
866
+ },
867
+ gt: {
868
+ symbol: ">",
869
+ label: "greater than",
870
+ example: "age>18"
871
+ },
872
+ gte: {
873
+ symbol: ">=",
874
+ label: "greater or equal",
875
+ example: "age>=18"
876
+ },
877
+ lt: {
878
+ symbol: "<",
879
+ label: "less than",
880
+ example: "age<65"
881
+ },
882
+ lte: {
883
+ symbol: "<=",
884
+ label: "less or equal",
885
+ example: "age<=65"
886
+ },
887
+ like: {
888
+ symbol: "~",
889
+ label: "like (case-sensitive)",
890
+ example: "name~John"
891
+ },
892
+ ilike: {
893
+ symbol: "~*",
894
+ label: "like (case-insensitive)",
895
+ example: "name~*john"
896
+ },
897
+ null: {
898
+ symbol: "=null",
899
+ label: "is null",
900
+ example: "deletedAt=null"
901
+ },
902
+ notNull: {
903
+ symbol: "!=null",
904
+ label: "is not null",
905
+ example: "email!=null"
906
+ },
907
+ in: {
908
+ symbol: "[...]",
909
+ label: "in array",
910
+ example: "status=[active,pending]"
911
+ }
912
+ };
913
+
914
+ //#endregion
915
+ //#region src/components/form/ControlQueryBuilder.tsx
916
+ /**
917
+ * Query builder with text input and help popover.
918
+ * Generates query strings for parseQueryString syntax.
919
+ */
920
+ const ControlQueryBuilder = ({ schema, value = "", onChange, placeholder = "Enter query or click help for assistance...",...textInputProps }) => {
921
+ const [helpOpened, setHelpOpened] = useState(false);
922
+ const [textValue, setTextValue] = useState(value);
923
+ const inputRef = useRef(null);
924
+ const fields = schema ? extractSchemaFields(schema) : [];
925
+ const handleTextChange = (newValue) => {
926
+ setTextValue(newValue);
927
+ onChange?.(newValue);
928
+ };
929
+ const handleClear = () => {
930
+ setTextValue("");
931
+ onChange?.("");
932
+ };
933
+ const handleInsert = (text) => {
934
+ const newValue = textValue ? `${textValue}${text} ` : `${text} `;
935
+ setTextValue(newValue);
936
+ onChange?.(newValue);
937
+ setTimeout(() => {
938
+ inputRef.current?.focus();
939
+ }, 0);
940
+ };
941
+ return /* @__PURE__ */ jsxs(Popover, {
942
+ width: 800,
943
+ position: "bottom-start",
944
+ shadow: "md",
945
+ opened: helpOpened,
946
+ onChange: setHelpOpened,
947
+ closeOnClickOutside: true,
948
+ closeOnEscape: true,
949
+ withArrow: true,
950
+ arrowSize: 14,
951
+ transitionProps: {
952
+ transition: "fade-down",
953
+ duration: 200,
954
+ timingFunction: "ease"
955
+ },
956
+ children: [/* @__PURE__ */ jsx(Popover.Target, { children: /* @__PURE__ */ jsx(TextInput, {
957
+ ref: inputRef,
958
+ placeholder,
959
+ value: textValue,
960
+ onChange: (e) => handleTextChange(e.currentTarget.value),
961
+ onFocus: () => setHelpOpened(true),
962
+ leftSection: /* @__PURE__ */ jsx(IconFilter, { size: 16 }),
963
+ rightSection: textValue && /* @__PURE__ */ jsx(ActionIcon, {
964
+ size: "sm",
965
+ variant: "subtle",
966
+ color: "gray",
967
+ onClick: handleClear,
968
+ children: /* @__PURE__ */ jsx(IconX, { size: 14 })
969
+ }),
970
+ ...textInputProps
971
+ }) }), /* @__PURE__ */ jsx(Popover.Dropdown, { children: /* @__PURE__ */ jsx(QueryHelp, {
972
+ fields,
973
+ onInsert: handleInsert
974
+ }) })]
975
+ });
976
+ };
977
+ function QueryHelp({ fields, onInsert }) {
978
+ return /* @__PURE__ */ jsxs(Group, {
979
+ gap: "md",
980
+ align: "flex-start",
981
+ wrap: "nowrap",
982
+ children: [
983
+ /* @__PURE__ */ jsxs(Stack, {
984
+ gap: "md",
985
+ style: { flex: 1 },
986
+ children: [
987
+ /* @__PURE__ */ jsxs(Stack, {
988
+ gap: "xs",
989
+ children: [/* @__PURE__ */ jsx(Text$1, {
990
+ size: "sm",
991
+ fw: 600,
992
+ children: "Operators"
993
+ }), /* @__PURE__ */ jsx(Stack, {
994
+ gap: 4,
995
+ children: Object.entries(OPERATOR_INFO).map(([key, info]) => /* @__PURE__ */ jsxs(Group, {
996
+ gap: "xs",
997
+ wrap: "nowrap",
998
+ children: [/* @__PURE__ */ jsx(Code, {
999
+ style: {
1000
+ minWidth: 35,
1001
+ textAlign: "center",
1002
+ cursor: "pointer"
1003
+ },
1004
+ onClick: () => onInsert(info.symbol),
1005
+ children: info.symbol
1006
+ }), /* @__PURE__ */ jsx(Text$1, {
1007
+ size: "xs",
1008
+ c: "dimmed",
1009
+ style: { flex: 1 },
1010
+ children: info.label
1011
+ })]
1012
+ }, key))
1013
+ })]
1014
+ }),
1015
+ /* @__PURE__ */ jsx(Divider, {}),
1016
+ /* @__PURE__ */ jsxs(Stack, {
1017
+ gap: "xs",
1018
+ children: [/* @__PURE__ */ jsx(Text$1, {
1019
+ size: "sm",
1020
+ fw: 600,
1021
+ children: "Logic"
1022
+ }), /* @__PURE__ */ jsxs(Stack, {
1023
+ gap: 4,
1024
+ children: [/* @__PURE__ */ jsxs(Group, {
1025
+ gap: "xs",
1026
+ wrap: "nowrap",
1027
+ children: [/* @__PURE__ */ jsx(Code, {
1028
+ style: {
1029
+ minWidth: 35,
1030
+ textAlign: "center",
1031
+ cursor: "pointer"
1032
+ },
1033
+ onClick: () => onInsert("&"),
1034
+ children: "&"
1035
+ }), /* @__PURE__ */ jsx(Text$1, {
1036
+ size: "xs",
1037
+ c: "dimmed",
1038
+ children: "AND"
1039
+ })]
1040
+ }), /* @__PURE__ */ jsxs(Group, {
1041
+ gap: "xs",
1042
+ wrap: "nowrap",
1043
+ children: [/* @__PURE__ */ jsx(Code, {
1044
+ style: {
1045
+ minWidth: 35,
1046
+ textAlign: "center",
1047
+ cursor: "pointer"
1048
+ },
1049
+ onClick: () => onInsert("|"),
1050
+ children: "|"
1051
+ }), /* @__PURE__ */ jsx(Text$1, {
1052
+ size: "xs",
1053
+ c: "dimmed",
1054
+ children: "OR"
1055
+ })]
1056
+ })]
1057
+ })]
1058
+ })
1059
+ ]
1060
+ }),
1061
+ fields.length > 0 && /* @__PURE__ */ jsx(Divider, { orientation: "vertical" }),
1062
+ fields.length > 0 && /* @__PURE__ */ jsxs(Stack, {
1063
+ gap: "xs",
1064
+ style: { flex: 2 },
1065
+ children: [/* @__PURE__ */ jsx(Text$1, {
1066
+ size: "sm",
1067
+ fw: 600,
1068
+ children: "Fields"
1069
+ }), /* @__PURE__ */ jsx(Stack, {
1070
+ gap: 4,
1071
+ style: {
1072
+ maxHeight: 300,
1073
+ overflowY: "auto"
1074
+ },
1075
+ children: fields.map((field) => /* @__PURE__ */ jsxs(Group, {
1076
+ gap: "xs",
1077
+ wrap: "nowrap",
1078
+ align: "flex-start",
1079
+ children: [
1080
+ /* @__PURE__ */ jsx(Code, {
1081
+ style: {
1082
+ minWidth: 120,
1083
+ cursor: "pointer"
1084
+ },
1085
+ onClick: () => onInsert(field.path),
1086
+ children: field.path
1087
+ }),
1088
+ /* @__PURE__ */ jsxs(Stack, {
1089
+ gap: 2,
1090
+ style: {
1091
+ flex: 1,
1092
+ minWidth: 0
1093
+ },
1094
+ children: [/* @__PURE__ */ jsx(Text$1, {
1095
+ size: "xs",
1096
+ c: "dimmed",
1097
+ lineClamp: 1,
1098
+ children: field.description || field.type
1099
+ }), field.enum && /* @__PURE__ */ jsx(Group, {
1100
+ gap: 4,
1101
+ wrap: "wrap",
1102
+ children: field.enum.map((enumValue) => /* @__PURE__ */ jsx(Code, {
1103
+ style: {
1104
+ cursor: "pointer",
1105
+ fontStyle: "italic",
1106
+ fontSize: "0.75rem"
1107
+ },
1108
+ c: "blue",
1109
+ onClick: () => onInsert(enumValue),
1110
+ children: enumValue
1111
+ }, enumValue))
1112
+ })]
1113
+ }),
1114
+ /* @__PURE__ */ jsx(Badge, {
1115
+ size: "xs",
1116
+ variant: "light",
1117
+ style: { flexShrink: 0 },
1118
+ children: field.type
1119
+ })
1120
+ ]
1121
+ }, field.path))
1122
+ })]
1123
+ })
1124
+ ]
1125
+ });
1126
+ }
1127
+ var ControlQueryBuilder_default = ControlQueryBuilder;
1128
+
708
1129
  //#endregion
709
1130
  //#region src/components/form/ControlSelect.tsx
710
1131
  /**
@@ -827,6 +1248,7 @@ var ControlSelect_default = ControlSelect;
827
1248
  * - DateInput (for date format)
828
1249
  * - DateTimePicker (for date-time format)
829
1250
  * - TimeInput (for time format)
1251
+ * - QueryBuilder (for building type-safe queries with autocomplete)
830
1252
  * - Custom component
831
1253
  *
832
1254
  * Automatically handles labels, descriptions, error messages, required state, and default icons.
@@ -838,6 +1260,16 @@ const Control = (_props) => {
838
1260
  ..._props,
839
1261
  ...schema.$control
840
1262
  };
1263
+ if (props.query) return /* @__PURE__ */ jsx(Input.Wrapper, {
1264
+ ...inputProps,
1265
+ children: /* @__PURE__ */ jsx(ControlQueryBuilder_default, {
1266
+ schema: props.query,
1267
+ value: props.input.props.value,
1268
+ onChange: (value) => {
1269
+ props.input.set(value);
1270
+ }
1271
+ })
1272
+ });
841
1273
  if (props.custom) {
842
1274
  const Custom = props.custom;
843
1275
  return /* @__PURE__ */ jsx(Input.Wrapper, {
@@ -855,14 +1287,13 @@ const Control = (_props) => {
855
1287
  });
856
1288
  }
857
1289
  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
1290
+ const controlNumberProps = typeof props.number === "object" ? props.number : {};
1291
+ return /* @__PURE__ */ jsx(ControlNumber_default, {
1292
+ input: props.input,
1293
+ title: props.title,
1294
+ description: props.description,
1295
+ icon,
1296
+ ...controlNumberProps
866
1297
  });
867
1298
  }
868
1299
  if (props.file) {
@@ -990,15 +1421,16 @@ var Control_default = Control;
990
1421
  */
991
1422
  const TypeForm = (props) => {
992
1423
  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) => {
1424
+ const schema = props.schema || form.options.schema;
1425
+ if (!schema?.properties) return null;
1426
+ const supportedFields = Object.keys(schema.properties).filter((fieldName) => {
995
1427
  const field = form.input[fieldName];
996
1428
  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;
1429
+ const schema$1 = field.schema;
1430
+ if ("type" in schema$1) {
1431
+ if (schema$1.type === "object") return false;
1000
1432
  }
1001
- if ("properties" in schema && schema.properties) return false;
1433
+ if ("properties" in schema$1 && schema$1.properties) return false;
1002
1434
  return true;
1003
1435
  });
1004
1436
  const colSpan = typeof columns === "number" ? {
@@ -1030,14 +1462,17 @@ const TypeForm = (props) => {
1030
1462
  const content = /* @__PURE__ */ jsxs(Flex$1, {
1031
1463
  direction: "column",
1032
1464
  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
- })] })]
1465
+ children: [renderFields(), !skipSubmitButton && /* @__PURE__ */ jsxs(Flex$1, {
1466
+ gap: "sm",
1467
+ children: [/* @__PURE__ */ jsx(ActionButton_default, {
1468
+ form,
1469
+ ...submitButtonProps,
1470
+ children: submitButtonProps?.children ?? "Submit"
1471
+ }), /* @__PURE__ */ jsx(ActionButton_default, {
1472
+ type: "reset",
1473
+ children: "Reset"
1474
+ })]
1475
+ })]
1041
1476
  });
1042
1477
  if (skipFormElement) return content;
1043
1478
  return /* @__PURE__ */ jsx("form", {
@@ -1053,7 +1488,8 @@ const ui = { colors: {
1053
1488
  transparent: "transparent",
1054
1489
  background: "var(--alepha-background)",
1055
1490
  surface: "var(--alepha-surface)",
1056
- elevated: "var(--alepha-elevated)"
1491
+ elevated: "var(--alepha-elevated)",
1492
+ border: "var(--alepha-border)"
1057
1493
  } };
1058
1494
 
1059
1495
  //#endregion
@@ -1159,16 +1595,24 @@ const Sidebar = (props) => {
1159
1595
  if (item.type === "search") return /* @__PURE__ */ jsx(OmnibarButton_default, { collapsed: props.collapsed }, key);
1160
1596
  if (item.type === "section") {
1161
1597
  if (props.collapsed) return;
1162
- return /* @__PURE__ */ jsx(Text$1, {
1163
- size: "xs",
1164
- c: "dimmed",
1598
+ return /* @__PURE__ */ jsxs(Flex$1, {
1165
1599
  mt: "md",
1166
1600
  mb: "xs",
1167
- mx: "sm",
1168
- tt: "uppercase",
1169
- fw: "bold",
1170
- children: item.label
1171
- }, key);
1601
+ align: "center",
1602
+ gap: "xs",
1603
+ children: [/* @__PURE__ */ jsx(ThemeIcon, {
1604
+ c: "dimmed",
1605
+ size: "xs",
1606
+ variant: "transparent",
1607
+ children: item.icon
1608
+ }), /* @__PURE__ */ jsx(Text$1, {
1609
+ size: "xs",
1610
+ c: "dimmed",
1611
+ tt: "uppercase",
1612
+ fw: "bold",
1613
+ children: item.label
1614
+ }, key)]
1615
+ });
1172
1616
  }
1173
1617
  }
1174
1618
  if ("element" in item) return /* @__PURE__ */ jsx(Flex$1, { children: item.element }, key);
@@ -1243,7 +1687,7 @@ const SidebarItem = (props) => {
1243
1687
  } }, []);
1244
1688
  if (level > maxLevel) return null;
1245
1689
  const handleItemClick = (e) => {
1246
- e.preventDefault();
1690
+ if (!props.item.target) e.preventDefault();
1247
1691
  if (item.children && item.children.length > 0) setIsOpen(!isOpen);
1248
1692
  else {
1249
1693
  props.onItemClick?.(item);
@@ -1258,6 +1702,7 @@ const SidebarItem = (props) => {
1258
1702
  w: "100%",
1259
1703
  justify: "space-between",
1260
1704
  href: props.item.href,
1705
+ target: props.item.target,
1261
1706
  variant: "subtle",
1262
1707
  size: props.item.theme?.size ?? props.theme.button?.size ?? (level === 0 ? "sm" : "xs"),
1263
1708
  variantActive: "default",
@@ -1316,7 +1761,7 @@ const SidebarCollapsedItem = (props) => {
1316
1761
  }, []);
1317
1762
  const [isOpen, setIsOpen] = useState(isActive(item));
1318
1763
  const handleItemClick = (e) => {
1319
- e.preventDefault();
1764
+ if (!props.item.target) e.preventDefault();
1320
1765
  if (item.children && item.children.length > 0) setIsOpen(!isOpen);
1321
1766
  else {
1322
1767
  props.onItemClick?.(item);
@@ -1331,6 +1776,7 @@ const SidebarCollapsedItem = (props) => {
1331
1776
  onClick: handleItemClick,
1332
1777
  icon: item.icon ?? /* @__PURE__ */ jsx(IconSquareRounded, {}),
1333
1778
  href: props.item.href,
1779
+ target: props.item.target,
1334
1780
  menu: item.children ? {
1335
1781
  position: "right",
1336
1782
  on: "hover",
@@ -1401,10 +1847,90 @@ var AdminShell_default = AdminShell;
1401
1847
  //#endregion
1402
1848
  //#region src/components/table/DataTable.tsx
1403
1849
  const DataTable = (props) => {
1404
- const [items, setItems] = useState(typeof props.items === "function" ? [] : props.items);
1850
+ const [items, setItems] = useState(typeof props.items === "function" ? { content: [] } : props.items);
1851
+ const defaultSize = props.infinityScroll ? 50 : props.defaultSize || 10;
1852
+ const [page, setPage] = useState(1);
1853
+ const [size, setSize] = useState(String(defaultSize));
1854
+ const [isLoadingMore, setIsLoadingMore] = useState(false);
1855
+ const [currentPage, setCurrentPage] = useState(0);
1856
+ const form = useForm({
1857
+ schema: t.object({
1858
+ ...props.filters ? props.filters.properties : {},
1859
+ page: t.number({ default: 0 }),
1860
+ size: t.number({ default: defaultSize }),
1861
+ sort: t.optional(t.string())
1862
+ }),
1863
+ handler: async (values, args) => {
1864
+ if (typeof props.items === "function") {
1865
+ const response = await props.items(values);
1866
+ if (props.infinityScroll && values.page > 0) setItems((prev) => ({
1867
+ ...response,
1868
+ content: [...prev.content, ...response.content]
1869
+ }));
1870
+ else setItems(response);
1871
+ setCurrentPage(values.page);
1872
+ setIsLoadingMore(false);
1873
+ }
1874
+ },
1875
+ onReset: async () => {
1876
+ setPage(1);
1877
+ setSize("10");
1878
+ await form.submit();
1879
+ },
1880
+ onChange: async (key, value) => {
1881
+ if (key === "page") {
1882
+ setPage(value + 1);
1883
+ await form.submit();
1884
+ return;
1885
+ }
1886
+ if (key === "size") {
1887
+ setSize(String(value));
1888
+ form.input.page.set(0);
1889
+ return;
1890
+ }
1891
+ }
1892
+ }, []);
1893
+ useDebouncedCallback(() => form.submit(), { delay: 1e3 });
1894
+ const dt = useInject(DateTimeProvider);
1895
+ useEffect(() => {
1896
+ if (props.submitOnInit) {
1897
+ console.log("submitting");
1898
+ form.submit();
1899
+ }
1900
+ if (props.submitEvery) {
1901
+ const it = dt.createInterval(() => {
1902
+ form.submit();
1903
+ }, props.submitEvery);
1904
+ return () => dt.clearInterval(it);
1905
+ }
1906
+ }, []);
1405
1907
  useEffect(() => {
1406
1908
  if (typeof props.items !== "function") setItems(props.items);
1407
1909
  }, [props.items]);
1910
+ useEffect(() => {
1911
+ if (!props.infinityScroll || typeof props.items !== "function") return;
1912
+ const handleScroll = () => {
1913
+ if (isLoadingMore) return;
1914
+ const scrollTop = window.scrollY;
1915
+ const windowHeight = window.innerHeight;
1916
+ const docHeight = document.documentElement.scrollHeight;
1917
+ if (scrollTop + windowHeight >= docHeight - 200) {
1918
+ const totalPages = items.page?.totalPages ?? 1;
1919
+ if (currentPage + 1 < totalPages) {
1920
+ setIsLoadingMore(true);
1921
+ form.input.page.set(currentPage + 1);
1922
+ }
1923
+ }
1924
+ };
1925
+ window.addEventListener("scroll", handleScroll);
1926
+ return () => window.removeEventListener("scroll", handleScroll);
1927
+ }, [
1928
+ props.infinityScroll,
1929
+ isLoadingMore,
1930
+ items.page?.totalPages,
1931
+ currentPage,
1932
+ form
1933
+ ]);
1408
1934
  const head = Object.entries(props.columns).map(([key, col]) => /* @__PURE__ */ jsx(Table.Th, { children: /* @__PURE__ */ jsx(ActionButton_default, {
1409
1935
  justify: "space-between",
1410
1936
  radius: 0,
@@ -1412,16 +1938,77 @@ const DataTable = (props) => {
1412
1938
  size: "xs",
1413
1939
  children: col.label
1414
1940
  }) }, key));
1415
- const rows = items.map((item, index) => {
1941
+ const rows = items.content.map((item, index) => {
1416
1942
  const trProps = props.tableTrProps ? props.tableTrProps(item) : {};
1417
1943
  return /* @__PURE__ */ jsx(Table.Tr, {
1418
1944
  ...trProps,
1419
- children: Object.entries(props.columns).map(([key, col]) => /* @__PURE__ */ jsx(Table.Td, { children: col.value(item) }, key))
1945
+ children: Object.entries(props.columns).map(([key, col]) => /* @__PURE__ */ jsx(Table.Td, { children: col.value(item, index) }, key))
1420
1946
  }, JSON.stringify(item));
1421
1947
  });
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 })]
1948
+ const schema = t.omit(form.options.schema, [
1949
+ "page",
1950
+ "size",
1951
+ "sort"
1952
+ ]);
1953
+ return /* @__PURE__ */ jsxs(Flex$1, {
1954
+ direction: "column",
1955
+ gap: "sm",
1956
+ flex: 1,
1957
+ children: [
1958
+ /* @__PURE__ */ jsx(Paper, {
1959
+ withBorder: true,
1960
+ p: "sm",
1961
+ children: props.filters ? /* @__PURE__ */ jsx(TypeForm_default, {
1962
+ form,
1963
+ schema
1964
+ }) : null
1965
+ }),
1966
+ /* @__PURE__ */ jsxs(Table, {
1967
+ striped: true,
1968
+ stripedColor: "",
1969
+ ...props.tableProps,
1970
+ children: [/* @__PURE__ */ jsx(Table.Thead, { children: /* @__PURE__ */ jsx(Table.Tr, { children: head }) }), /* @__PURE__ */ jsx(Table.Tbody, { children: rows })]
1971
+ }),
1972
+ !props.infinityScroll && /* @__PURE__ */ jsxs(Flex$1, {
1973
+ justify: "space-between",
1974
+ align: "center",
1975
+ children: [/* @__PURE__ */ jsx(Pagination, {
1976
+ withEdges: true,
1977
+ total: items.page?.totalPages ?? 1,
1978
+ value: page,
1979
+ onChange: (value) => {
1980
+ form.input.page.set(value - 1);
1981
+ }
1982
+ }), /* @__PURE__ */ jsx(Flex$1, { children: /* @__PURE__ */ jsx(Select, {
1983
+ value: size,
1984
+ onChange: (value) => {
1985
+ form.input.size.set(Number(value));
1986
+ },
1987
+ data: [
1988
+ {
1989
+ value: "5",
1990
+ label: "5"
1991
+ },
1992
+ {
1993
+ value: "10",
1994
+ label: "10"
1995
+ },
1996
+ {
1997
+ value: "25",
1998
+ label: "25"
1999
+ },
2000
+ {
2001
+ value: "50",
2002
+ label: "50"
2003
+ },
2004
+ {
2005
+ value: "100",
2006
+ label: "100"
2007
+ }
2008
+ ]
2009
+ }) })]
2010
+ })
2011
+ ]
1425
2012
  });
1426
2013
  };
1427
2014
  var DataTable_default = DataTable;
@@ -1465,5 +2052,5 @@ const AlephaUI = $module({
1465
2052
  });
1466
2053
 
1467
2054
  //#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 };
1469
- //# sourceMappingURL=index.mjs.map
2055
+ 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, 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 };
2056
+ //# sourceMappingURL=index.js.map