@dyrected/admin 2.5.38 → 2.5.40

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/admin.css CHANGED
@@ -1828,6 +1828,10 @@
1828
1828
  width: 2.25rem;
1829
1829
  }
1830
1830
 
1831
+ .dy-w-96{
1832
+ width: 24rem;
1833
+ }
1834
+
1831
1835
  .dy-w-\[--cell-size\]{
1832
1836
  width: var(--cell-size);
1833
1837
  }
@@ -1965,6 +1969,11 @@
1965
1969
  min-width: var(--radix-select-trigger-width);
1966
1970
  }
1967
1971
 
1972
+ .dy-min-w-max{
1973
+ min-width: -moz-max-content;
1974
+ min-width: max-content;
1975
+ }
1976
+
1968
1977
  .dy-max-w-2xl{
1969
1978
  max-width: 42rem;
1970
1979
  }
@@ -4202,6 +4211,12 @@
4202
4211
  --cell-size: 2.25rem;
4203
4212
  }
4204
4213
 
4214
+ .\[appearance\:textfield\]{
4215
+ -webkit-appearance: textfield;
4216
+ -moz-appearance: textfield;
4217
+ appearance: textfield;
4218
+ }
4219
+
4205
4220
  .dy-admin-ui .media-preview-dialod-details-form div[style="min-width: 100%;display: table;"] {
4206
4221
  display: flex !important;
4207
4222
  }
@@ -5595,6 +5610,10 @@
5595
5610
  width: 95vw;
5596
5611
  }
5597
5612
 
5613
+ .sm\:dy-w-\[var\(--field-w\)\]{
5614
+ width: var(--field-w);
5615
+ }
5616
+
5598
5617
  .sm\:dy-w-auto{
5599
5618
  width: auto;
5600
5619
  }
@@ -5826,11 +5845,6 @@
5826
5845
  line-height: 1.75rem;
5827
5846
  }
5828
5847
 
5829
- .sm\:dy-text-sm{
5830
- font-size: 0.875rem;
5831
- line-height: 1.25rem;
5832
- }
5833
-
5834
5848
  .sm\:dy-text-xl{
5835
5849
  font-size: 1.25rem;
5836
5850
  line-height: 1.75rem;
@@ -6179,6 +6193,16 @@
6179
6193
  }
6180
6194
  }
6181
6195
 
6196
+ .\[\&\:\:-webkit-inner-spin-button\]\:dy-appearance-none::-webkit-inner-spin-button{
6197
+ -webkit-appearance: none;
6198
+ appearance: none;
6199
+ }
6200
+
6201
+ .\[\&\:\:-webkit-outer-spin-button\]\:dy-appearance-none::-webkit-outer-spin-button{
6202
+ -webkit-appearance: none;
6203
+ appearance: none;
6204
+ }
6205
+
6182
6206
  .\[\&\:has\(\[role\=checkbox\]\)\]\:dy-pr-0:has([role=checkbox]){
6183
6207
  padding-right: 0px;
6184
6208
  }
package/dist/index.mjs CHANGED
@@ -14,7 +14,7 @@ import { cva } from "class-variance-authority";
14
14
  import { Toaster, toast } from "sonner";
15
15
  import * as PopoverPrimitive from "@radix-ui/react-popover";
16
16
  import * as SelectPrimitive from "@radix-ui/react-select";
17
- import { flexRender, getCoreRowModel, getFilteredRowModel, getPaginationRowModel, getSortedRowModel, useReactTable } from "@tanstack/react-table";
17
+ import { flexRender, getCoreRowModel, getFilteredRowModel, getSortedRowModel, useReactTable } from "@tanstack/react-table";
18
18
  import * as CheckboxPrimitive from "@radix-ui/react-checkbox";
19
19
  import { DndContext, KeyboardSensor, PointerSensor, closestCenter, useSensor, useSensors } from "@dnd-kit/core";
20
20
  import { SortableContext, arrayMove, sortableKeyboardCoordinates, useSortable, verticalListSortingStrategy } from "@dnd-kit/sortable";
@@ -1810,7 +1810,6 @@ function DataTable({ columns, data, searchKey, rowSelection: externalRowSelectio
1810
1810
  onSortingChange: setSorting,
1811
1811
  onColumnFiltersChange: setColumnFilters,
1812
1812
  getCoreRowModel: getCoreRowModel(),
1813
- getPaginationRowModel: getPaginationRowModel(),
1814
1813
  getSortedRowModel: getSortedRowModel(),
1815
1814
  getFilteredRowModel: getFilteredRowModel(),
1816
1815
  onColumnVisibilityChange: setColumnVisibility,
@@ -1824,111 +1823,65 @@ function DataTable({ columns, data, searchKey, rowSelection: externalRowSelectio
1824
1823
  });
1825
1824
  return /* @__PURE__ */ jsxs("div", {
1826
1825
  className: "dy-w-full dy-space-y-4",
1827
- children: [
1828
- /* @__PURE__ */ jsxs("div", {
1829
- className: "dy-flex dy-flex-col dy-gap-3 sm:dy-flex-row sm:dy-items-center sm:dy-gap-4",
1830
- children: [
1831
- /* @__PURE__ */ jsxs("div", {
1832
- className: `dy-order-1 dy-grid dy-w-full dy-gap-2 sm:dy-order-3 sm:dy-ml-auto sm:dy-flex sm:dy-w-auto sm:dy-items-center ${toolbarActions ? hideViewButton ? "dy-grid-cols-2" : "dy-grid-cols-3" : hideViewButton ? "dy-grid-cols-1" : "dy-grid-cols-2"}`,
1833
- children: [toolbarActions, !hideViewButton && /* @__PURE__ */ jsxs(DropdownMenu, { children: [/* @__PURE__ */ jsx(DropdownMenuTrigger, {
1834
- asChild: true,
1835
- children: /* @__PURE__ */ jsxs(Button, {
1836
- variant: "outline",
1837
- size: "sm",
1838
- className: "dy-flex dy-h-9 dy-w-full dy-gap-2 sm:dy-h-8 sm:dy-w-auto",
1839
- children: [/* @__PURE__ */ jsx(Settings2, { className: "dy-h-4 dy-w-4" }), "View"]
1840
- })
1841
- }), /* @__PURE__ */ jsx(DropdownMenuContent, {
1842
- align: "end",
1843
- children: table.getAllColumns().filter((column) => column.getCanHide()).map((column) => {
1844
- return /* @__PURE__ */ jsx(DropdownMenuCheckboxItem, {
1845
- className: "dy-capitalize",
1846
- checked: column.getIsVisible(),
1847
- onCheckedChange: (value) => column.toggleVisibility(!!value),
1848
- children: column.id
1849
- }, column.id);
1850
- })
1851
- })] })]
1852
- }),
1853
- searchKey && /* @__PURE__ */ jsx(Input, {
1854
- placeholder: `Search ${searchKey}...`,
1855
- value: table.getColumn(searchKey)?.getFilterValue() ?? "",
1856
- onChange: (event) => table.getColumn(searchKey)?.setFilterValue(event.target.value),
1857
- className: "dy-order-2 dy-h-9 dy-w-full sm:dy-order-1 sm:dy-max-w-sm"
1858
- }),
1859
- bulkActions && table.getFilteredSelectedRowModel().rows.length > 0 && /* @__PURE__ */ jsx("div", {
1860
- className: "dy-order-3 dy-flex dy-w-full dy-items-center dy-gap-2 dy-animate-in dy-slide-in-from-left-2 sm:dy-order-2 sm:dy-w-auto",
1861
- children: bulkActions(table.getFilteredSelectedRowModel().rows.map((r) => r.original.id))
1862
- })
1863
- ]
1864
- }),
1865
- /* @__PURE__ */ jsx("div", {
1866
- className: "dy-overflow-x-auto dy-rounded-md dy-border dy-border-border/40",
1867
- children: /* @__PURE__ */ jsxs(Table$1, {
1868
- className: "dy-min-w-[720px]",
1869
- children: [/* @__PURE__ */ jsx(TableHeader, { children: table.getHeaderGroups().map((headerGroup) => /* @__PURE__ */ jsx(TableRow, { children: headerGroup.headers.map((header) => {
1870
- return /* @__PURE__ */ jsx(TableHead, { children: header.isPlaceholder ? null : flexRender(header.column.columnDef.header, header.getContext()) }, header.id);
1871
- }) }, headerGroup.id)) }), /* @__PURE__ */ jsx(TableBody, {
1872
- className: "dy-border-t dy-border-border/40",
1873
- children: table.getRowModel().rows?.length ? table.getRowModel().rows.map((row) => /* @__PURE__ */ jsx(TableRow, {
1874
- "data-state": row.getIsSelected() && "selected",
1875
- className: "dy-border-none even:dy-bg-primary/[0.03] hover:dy-bg-primary/[0.06] dy-transition-colors dy-duration-200",
1876
- children: row.getVisibleCells().map((cell) => /* @__PURE__ */ jsx(TableCell, {
1877
- className: "dy-py-4 dy-px-4 dy-border-none first:dy-pl-4 last:dy-pr-4",
1878
- children: flexRender(cell.column.columnDef.cell, cell.getContext())
1879
- }, cell.id))
1880
- }, row.id)) : /* @__PURE__ */ jsx(TableRow, { children: /* @__PURE__ */ jsx(TableCell, {
1881
- colSpan: columns.length,
1882
- className: "dy-h-24 dy-text-center",
1883
- children: "No results."
1884
- }) })
1885
- })]
1886
- })
1887
- }),
1888
- /* @__PURE__ */ jsxs("div", {
1889
- className: "dy-flex dy-flex-col dy-gap-3 dy-px-2 sm:dy-flex-row sm:dy-items-center sm:dy-justify-between",
1890
- children: [/* @__PURE__ */ jsxs("div", {
1891
- className: "dy-flex-1 dy-text-xs dy-text-muted-foreground sm:dy-text-sm",
1892
- children: [
1893
- table.getFilteredSelectedRowModel().rows.length,
1894
- " of",
1895
- " ",
1896
- table.getFilteredRowModel().rows.length,
1897
- " row(s) selected."
1898
- ]
1899
- }), /* @__PURE__ */ jsxs("div", {
1900
- className: "dy-flex dy-items-center dy-justify-between dy-gap-2 sm:dy-justify-end",
1901
- children: [
1902
- /* @__PURE__ */ jsx(Button, {
1903
- variant: "outline",
1904
- size: "sm",
1905
- className: "dy-h-9 dy-w-9 dy-p-0",
1906
- onClick: () => table.previousPage(),
1907
- disabled: !table.getCanPreviousPage(),
1908
- children: /* @__PURE__ */ jsx(ChevronLeft, { className: "dy-h-4 dy-w-4" })
1909
- }),
1910
- /* @__PURE__ */ jsxs("span", {
1911
- className: "dy-min-w-0 dy-text-center dy-text-xs dy-font-medium sm:dy-text-sm",
1912
- children: [
1913
- "Page ",
1914
- table.getState().pagination.pageIndex + 1,
1915
- " of",
1916
- " ",
1917
- table.getPageCount()
1918
- ]
1919
- }),
1920
- /* @__PURE__ */ jsx(Button, {
1826
+ children: [/* @__PURE__ */ jsxs("div", {
1827
+ className: "dy-flex dy-flex-col dy-gap-3 sm:dy-flex-row sm:dy-items-center sm:dy-gap-4",
1828
+ children: [
1829
+ /* @__PURE__ */ jsxs("div", {
1830
+ className: `dy-order-1 dy-grid dy-w-full dy-gap-2 sm:dy-order-3 sm:dy-ml-auto sm:dy-flex sm:dy-w-auto sm:dy-items-center ${toolbarActions ? hideViewButton ? "dy-grid-cols-2" : "dy-grid-cols-3" : hideViewButton ? "dy-grid-cols-1" : "dy-grid-cols-2"}`,
1831
+ children: [toolbarActions, !hideViewButton && /* @__PURE__ */ jsxs(DropdownMenu, { children: [/* @__PURE__ */ jsx(DropdownMenuTrigger, {
1832
+ asChild: true,
1833
+ children: /* @__PURE__ */ jsxs(Button, {
1921
1834
  variant: "outline",
1922
1835
  size: "sm",
1923
- className: "dy-h-9 dy-w-9 dy-p-0",
1924
- onClick: () => table.nextPage(),
1925
- disabled: !table.getCanNextPage(),
1926
- children: /* @__PURE__ */ jsx(ChevronRight, { className: "dy-h-4 dy-w-4" })
1836
+ className: "dy-flex dy-h-9 dy-w-full dy-gap-2 sm:dy-h-8 sm:dy-w-auto",
1837
+ children: [/* @__PURE__ */ jsx(Settings2, { className: "dy-h-4 dy-w-4" }), "View"]
1927
1838
  })
1928
- ]
1839
+ }), /* @__PURE__ */ jsx(DropdownMenuContent, {
1840
+ align: "end",
1841
+ children: table.getAllColumns().filter((column) => column.getCanHide()).map((column) => {
1842
+ return /* @__PURE__ */ jsx(DropdownMenuCheckboxItem, {
1843
+ className: "dy-capitalize",
1844
+ checked: column.getIsVisible(),
1845
+ onCheckedChange: (value) => column.toggleVisibility(!!value),
1846
+ children: column.id
1847
+ }, column.id);
1848
+ })
1849
+ })] })]
1850
+ }),
1851
+ searchKey && /* @__PURE__ */ jsx(Input, {
1852
+ placeholder: `Search ${searchKey}...`,
1853
+ value: table.getColumn(searchKey)?.getFilterValue() ?? "",
1854
+ onChange: (event) => table.getColumn(searchKey)?.setFilterValue(event.target.value),
1855
+ className: "dy-order-2 dy-h-9 dy-w-full sm:dy-order-1 sm:dy-max-w-sm"
1856
+ }),
1857
+ bulkActions && table.getFilteredSelectedRowModel().rows.length > 0 && /* @__PURE__ */ jsx("div", {
1858
+ className: "dy-order-3 dy-flex dy-w-full dy-items-center dy-gap-2 dy-animate-in dy-slide-in-from-left-2 sm:dy-order-2 sm:dy-w-auto",
1859
+ children: bulkActions(table.getFilteredSelectedRowModel().rows.map((r) => r.original.id))
1860
+ })
1861
+ ]
1862
+ }), /* @__PURE__ */ jsx("div", {
1863
+ className: "dy-overflow-x-auto dy-rounded-md dy-border dy-border-border/40",
1864
+ children: /* @__PURE__ */ jsxs(Table$1, {
1865
+ className: "dy-min-w-[720px]",
1866
+ children: [/* @__PURE__ */ jsx(TableHeader, { children: table.getHeaderGroups().map((headerGroup) => /* @__PURE__ */ jsx(TableRow, { children: headerGroup.headers.map((header) => {
1867
+ return /* @__PURE__ */ jsx(TableHead, { children: header.isPlaceholder ? null : flexRender(header.column.columnDef.header, header.getContext()) }, header.id);
1868
+ }) }, headerGroup.id)) }), /* @__PURE__ */ jsx(TableBody, {
1869
+ className: "dy-border-t dy-border-border/40",
1870
+ children: table.getRowModel().rows?.length ? table.getRowModel().rows.map((row) => /* @__PURE__ */ jsx(TableRow, {
1871
+ "data-state": row.getIsSelected() && "selected",
1872
+ className: "dy-border-none even:dy-bg-primary/[0.03] hover:dy-bg-primary/[0.06] dy-transition-colors dy-duration-200",
1873
+ children: row.getVisibleCells().map((cell) => /* @__PURE__ */ jsx(TableCell, {
1874
+ className: "dy-py-4 dy-px-4 dy-border-none first:dy-pl-4 last:dy-pr-4",
1875
+ children: flexRender(cell.column.columnDef.cell, cell.getContext())
1876
+ }, cell.id))
1877
+ }, row.id)) : /* @__PURE__ */ jsx(TableRow, { children: /* @__PURE__ */ jsx(TableCell, {
1878
+ colSpan: columns.length,
1879
+ className: "dy-h-24 dy-text-center",
1880
+ children: "No results."
1881
+ }) })
1929
1882
  })]
1930
1883
  })
1931
- ]
1884
+ })]
1932
1885
  });
1933
1886
  }
1934
1887
  //#endregion
@@ -2092,7 +2045,7 @@ function CsvImporter({ slug, schema, onClose }) {
2092
2045
  const strVal = String(val).trim();
2093
2046
  switch (field.type) {
2094
2047
  case "number": {
2095
- const num = Number(strVal);
2048
+ const num = Number(strVal.replace(/[$€£¥₹₦₩₪₺₽฿,]/g, ""));
2096
2049
  if (isNaN(num)) return {
2097
2050
  value: null,
2098
2051
  error: "Must be a valid number"
@@ -2117,6 +2070,28 @@ function CsvImporter({ slug, schema, onClose }) {
2117
2070
  };
2118
2071
  return { value: date.toISOString() };
2119
2072
  }
2073
+ case "image": try {
2074
+ new URL(strVal);
2075
+ return { value: strVal };
2076
+ } catch {
2077
+ return {
2078
+ value: null,
2079
+ error: "Must be a valid image URL"
2080
+ };
2081
+ }
2082
+ case "url": try {
2083
+ new URL(strVal);
2084
+ return { value: {
2085
+ type: "custom",
2086
+ url: strVal,
2087
+ label: ""
2088
+ } };
2089
+ } catch {
2090
+ return {
2091
+ value: null,
2092
+ error: "Must be a valid URL (e.g. https://example.com)"
2093
+ };
2094
+ }
2120
2095
  case "select":
2121
2096
  case "radio":
2122
2097
  if (field.options && Array.isArray(field.options) && field.options.length > 0) {
@@ -2186,7 +2161,21 @@ function CsvImporter({ slug, schema, onClose }) {
2186
2161
  continue;
2187
2162
  }
2188
2163
  try {
2189
- await client.collection(slug).create(rowResult.data);
2164
+ const data = { ...rowResult.data };
2165
+ for (const field of importableFields) {
2166
+ if (field.type !== "image") continue;
2167
+ const url = data[field.name];
2168
+ if (!url || typeof url !== "string") continue;
2169
+ const mediaCollection = field.relationTo || "media";
2170
+ const response = await fetch(url);
2171
+ if (!response.ok) throw new Error(`Failed to fetch image: ${url}`);
2172
+ const blob = await response.blob();
2173
+ const filename = url.split("/").pop()?.split("?")[0] || "image";
2174
+ const file = new File([blob], filename, { type: blob.type });
2175
+ const media = await client.collection(mediaCollection).upload(file);
2176
+ data[field.name] = media.id;
2177
+ }
2178
+ await client.collection(slug).create(data);
2190
2179
  success++;
2191
2180
  setSuccessCount(success);
2192
2181
  } catch (error) {
@@ -2267,8 +2256,8 @@ function CsvImporter({ slug, schema, onClose }) {
2267
2256
  className: "dy-p-4 dy-bg-muted/40 dy-border dy-border-border dy-rounded-xl dy-text-sm dy-text-muted-foreground",
2268
2257
  children: "Map the columns in your CSV file to the corresponding fields of the collection. All required fields must be mapped to proceed."
2269
2258
  }),
2270
- /* @__PURE__ */ jsx(ScrollArea, {
2271
- className: "dy-h-[300px] dy-pr-4 dy-border dy-border-border dy-rounded-xl",
2259
+ /* @__PURE__ */ jsx("div", {
2260
+ className: "dy-h-[300px] dy-overflow-y-auto dy-border dy-border-border dy-rounded-xl",
2272
2261
  children: /* @__PURE__ */ jsx("div", {
2273
2262
  className: "dy-divide-y dy-divide-border",
2274
2263
  children: csvHeaders.map((header) => {
@@ -2361,10 +2350,10 @@ function CsvImporter({ slug, schema, onClose }) {
2361
2350
  ]
2362
2351
  })]
2363
2352
  }),
2364
- /* @__PURE__ */ jsx(ScrollArea, {
2365
- className: "dy-h-[300px] dy-border dy-border-border dy-rounded-xl",
2353
+ /* @__PURE__ */ jsx("div", {
2354
+ className: "dy-h-[300px] dy-overflow-auto dy-border dy-border-border dy-rounded-xl",
2366
2355
  children: /* @__PURE__ */ jsxs("table", {
2367
- className: "dy-w-full dy-text-left dy-border-collapse",
2356
+ className: "dy-w-full dy-min-w-max dy-text-left dy-border-collapse",
2368
2357
  children: [/* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", {
2369
2358
  className: "dy-border-b dy-border-border dy-bg-muted/40 dy-text-xs dy-font-semibold dy-text-muted-foreground",
2370
2359
  children: [/* @__PURE__ */ jsx("th", {
@@ -8358,8 +8347,8 @@ function FormEngine({ collection, fields, defaultValues = {}, onSubmit, onChange
8358
8347
  const tabbedFields = visibleFields.filter((f) => !!f.admin?.tab);
8359
8348
  let fieldsContent;
8360
8349
  const renderFieldColumn = (field) => /* @__PURE__ */ jsx("div", {
8361
- className: "dy-min-w-0 dy-px-3",
8362
- style: { width: field.admin?.width || "100%" },
8350
+ className: "dy-min-w-0 dy-px-3 dy-w-full sm:dy-w-[var(--field-w)]",
8351
+ style: { "--field-w": field.admin?.width || "100%" },
8363
8352
  children: /* @__PURE__ */ jsx(FormFieldRenderer, {
8364
8353
  schema: field,
8365
8354
  basePath: "",
@@ -8765,7 +8754,7 @@ function RelationshipPicker({ value, onChange, label, relationTo, multiple, disa
8765
8754
  }
8766
8755
  //#endregion
8767
8756
  //#region src/components/ui/spreadsheet-editor.tsx
8768
- function SelectCellDisplay({ field, value, collection, siblingValues }) {
8757
+ function useFieldOptions(field, collection, siblingValues) {
8769
8758
  const { client } = useDyrected();
8770
8759
  const isDynamic = !!(field.options && typeof field.options === "object" && "_dynamic" in field.options);
8771
8760
  const { data: dynamicOptions } = useQuery({
@@ -8777,7 +8766,7 @@ function SelectCellDisplay({ field, value, collection, siblingValues }) {
8777
8766
  ],
8778
8767
  queryFn: async () => {
8779
8768
  const q = new URLSearchParams();
8780
- if (siblingValues) Object.entries(siblingValues).forEach(([k, v]) => {
8769
+ Object.entries(siblingValues).forEach(([k, v]) => {
8781
8770
  if (typeof v === "string" || typeof v === "number" || typeof v === "boolean") q.append(k, String(v));
8782
8771
  });
8783
8772
  const url = `${client?.getBaseUrl() || ""}/api/dyrected/options/${collection}/${field.name}?${q.toString()}`;
@@ -8793,7 +8782,7 @@ function SelectCellDisplay({ field, value, collection, siblingValues }) {
8793
8782
  },
8794
8783
  enabled: !!client && isDynamic && !!collection && !!field.name
8795
8784
  });
8796
- const matched = React$1.useMemo(() => {
8785
+ return React$1.useMemo(() => {
8797
8786
  const rawOptions = isDynamic ? dynamicOptions || [] : field.options;
8798
8787
  return (Array.isArray(rawOptions) ? rawOptions : []).map((opt) => {
8799
8788
  if (typeof opt === "object" && opt !== null) return {
@@ -8809,54 +8798,14 @@ function SelectCellDisplay({ field, value, collection, siblingValues }) {
8809
8798
  isDynamic,
8810
8799
  dynamicOptions,
8811
8800
  field.options
8812
- ]).find((opt) => opt.value === String(value ?? ""));
8801
+ ]);
8802
+ }
8803
+ function SelectCellDisplay({ field, value, collection, siblingValues }) {
8804
+ const matched = useFieldOptions(field, collection, siblingValues).find((opt) => opt.value === String(value ?? ""));
8813
8805
  return /* @__PURE__ */ jsx("span", { children: matched ? matched.label : value !== null && value !== void 0 ? String(value) : "" });
8814
8806
  }
8815
8807
  function SelectPopoverEditor({ field, value, collection, siblingValues, onCommit }) {
8816
- const { client } = useDyrected();
8817
- const isDynamic = !!(field.options && typeof field.options === "object" && "_dynamic" in field.options);
8818
- const { data: dynamicOptions } = useQuery({
8819
- queryKey: [
8820
- "options",
8821
- collection,
8822
- field.name,
8823
- siblingValues
8824
- ],
8825
- queryFn: async () => {
8826
- const q = new URLSearchParams();
8827
- if (siblingValues) Object.entries(siblingValues).forEach(([k, v]) => {
8828
- if (typeof v === "string" || typeof v === "number" || typeof v === "boolean") q.append(k, String(v));
8829
- });
8830
- const url = `${client?.getBaseUrl() || ""}/api/dyrected/options/${collection}/${field.name}?${q.toString()}`;
8831
- const authHeaders = {};
8832
- const token = typeof window !== "undefined" ? localStorage.getItem("dyrected_token") : null;
8833
- if (token) authHeaders["Authorization"] = `Bearer ${token}`;
8834
- const res = await fetch(url, { headers: {
8835
- "Content-Type": "application/json",
8836
- ...authHeaders
8837
- } });
8838
- if (!res.ok) throw new Error("Failed to fetch options");
8839
- return res.json();
8840
- },
8841
- enabled: !!client && isDynamic && !!collection && !!field.name
8842
- });
8843
- const options = React$1.useMemo(() => {
8844
- const rawOptions = isDynamic ? dynamicOptions || [] : field.options;
8845
- return (Array.isArray(rawOptions) ? rawOptions : []).map((opt) => {
8846
- if (typeof opt === "object" && opt !== null) return {
8847
- label: opt.label,
8848
- value: String(opt.value ?? "")
8849
- };
8850
- return {
8851
- label: String(opt),
8852
- value: String(opt)
8853
- };
8854
- });
8855
- }, [
8856
- isDynamic,
8857
- dynamicOptions,
8858
- field.options
8859
- ]);
8808
+ const options = useFieldOptions(field, collection, siblingValues);
8860
8809
  return /* @__PURE__ */ jsxs("div", {
8861
8810
  className: "dy-max-h-60 dy-overflow-y-auto dy-flex dy-flex-col dy-divide-y dy-divide-border dy-bg-popover dy-rounded-md",
8862
8811
  children: [options.map((opt) => /* @__PURE__ */ jsx("button", {
@@ -8870,6 +8819,163 @@ function SelectPopoverEditor({ field, value, collection, siblingValues, onCommit
8870
8819
  })]
8871
8820
  });
8872
8821
  }
8822
+ function MultiSelectCellDisplay({ field, value, collection, siblingValues }) {
8823
+ const options = useFieldOptions(field, collection, siblingValues);
8824
+ const labels = (Array.isArray(value) ? value.map(String) : []).map((v) => options.find((o) => o.value === v)?.label ?? v);
8825
+ return /* @__PURE__ */ jsx("span", { children: labels.length > 0 ? labels.join(", ") : "" });
8826
+ }
8827
+ function MultiSelectPopoverEditor({ field, value, collection, siblingValues, onToggle }) {
8828
+ const options = useFieldOptions(field, collection, siblingValues);
8829
+ const selected = Array.isArray(value) ? value.map(String) : [];
8830
+ return /* @__PURE__ */ jsxs("div", {
8831
+ className: "dy-max-h-60 dy-overflow-y-auto dy-flex dy-flex-col dy-divide-y dy-divide-border dy-bg-popover dy-rounded-md",
8832
+ children: [options.map((opt) => {
8833
+ const checked = selected.includes(opt.value);
8834
+ return /* @__PURE__ */ jsxs("button", {
8835
+ type: "button",
8836
+ className: cn("dy-w-full dy-text-left dy-px-3 dy-py-2 dy-text-xs hover:dy-bg-accent hover:dy-text-accent-foreground dy-transition-colors dy-flex dy-items-center dy-gap-2", checked && "dy-bg-accent/50"),
8837
+ onClick: () => onToggle(checked ? selected.filter((v) => v !== opt.value) : [...selected, opt.value]),
8838
+ children: [/* @__PURE__ */ jsx("span", {
8839
+ className: cn("dy-w-3.5 dy-h-3.5 dy-rounded-sm dy-border dy-border-border dy-flex-shrink-0 dy-flex dy-items-center dy-justify-center", checked && "dy-bg-primary dy-border-primary"),
8840
+ children: checked && /* @__PURE__ */ jsx("span", {
8841
+ className: "dy-text-primary-foreground dy-text-[10px] dy-leading-none",
8842
+ children: "✓"
8843
+ })
8844
+ }), opt.label]
8845
+ }, opt.value);
8846
+ }), options.length === 0 && /* @__PURE__ */ jsx("div", {
8847
+ className: "dy-text-xs dy-text-muted-foreground dy-p-3 dy-text-center",
8848
+ children: "No options available"
8849
+ })]
8850
+ });
8851
+ }
8852
+ function SubFieldRow({ subField, value, onChange, collection }) {
8853
+ const { user, schemas } = useDyrected();
8854
+ const handleChange = React$1.useCallback((eventOrValue) => {
8855
+ if (eventOrValue != null && typeof eventOrValue === "object" && "target" in eventOrValue) {
8856
+ const e = eventOrValue;
8857
+ if (e.target.type === "checkbox") onChange(e.target.checked);
8858
+ else if (e.target.value === "") onChange(null);
8859
+ else if (subField.type === "number") onChange(Number(e.target.value));
8860
+ else onChange(e.target.value);
8861
+ } else onChange(eventOrValue);
8862
+ }, [onChange, subField.type]);
8863
+ const syntheticField = React$1.useMemo(() => ({
8864
+ value: value ?? (subField.type === "boolean" ? false : ""),
8865
+ onChange: handleChange,
8866
+ name: subField.name ?? "",
8867
+ ref: { current: null }
8868
+ }), [
8869
+ value,
8870
+ handleChange,
8871
+ subField.name,
8872
+ subField.type
8873
+ ]);
8874
+ return /* @__PURE__ */ jsxs("div", {
8875
+ className: "dy-flex dy-flex-col dy-gap-0.5",
8876
+ children: [/* @__PURE__ */ jsx("label", {
8877
+ className: "dy-text-[10px] dy-text-muted-foreground",
8878
+ children: subField.label || subField.name
8879
+ }), /* @__PURE__ */ jsx(FieldRenderer, {
8880
+ schema: subField,
8881
+ field: syntheticField,
8882
+ collection,
8883
+ context: {
8884
+ user: user ?? null,
8885
+ schemas,
8886
+ siblingData: {}
8887
+ }
8888
+ })]
8889
+ });
8890
+ }
8891
+ function ObjectPopoverEditor({ field, value, onChange, collection }) {
8892
+ const subFields = field.fields || [];
8893
+ const obj = typeof value === "object" && value !== null && !Array.isArray(value) ? value : {};
8894
+ return /* @__PURE__ */ jsxs("div", {
8895
+ className: "dy-flex dy-flex-col dy-gap-3 dy-max-h-[400px] dy-overflow-y-auto",
8896
+ children: [
8897
+ /* @__PURE__ */ jsx("div", {
8898
+ className: "dy-text-xs dy-font-semibold dy-text-muted-foreground",
8899
+ children: field.label || field.name
8900
+ }),
8901
+ subFields.filter((sf) => ![
8902
+ "object",
8903
+ "array",
8904
+ "blocks",
8905
+ "join",
8906
+ "row",
8907
+ "richText"
8908
+ ].includes(sf.type)).map((sf) => /* @__PURE__ */ jsx(SubFieldRow, {
8909
+ subField: sf,
8910
+ value: obj[sf.name],
8911
+ onChange: (val) => onChange({
8912
+ ...obj,
8913
+ [sf.name]: val
8914
+ }),
8915
+ collection
8916
+ }, sf.name)),
8917
+ subFields.length === 0 && /* @__PURE__ */ jsx("div", {
8918
+ className: "dy-text-xs dy-text-muted-foreground",
8919
+ children: "No fields defined"
8920
+ })
8921
+ ]
8922
+ });
8923
+ }
8924
+ function ArrayPopoverEditor({ field, value, onChange, collection }) {
8925
+ const subFields = field.fields || [];
8926
+ const items = Array.isArray(value) ? value : [];
8927
+ const updateItem = (idx, key, val) => onChange(items.map((item, i) => i === idx ? {
8928
+ ...item,
8929
+ [key]: val
8930
+ } : item));
8931
+ return /* @__PURE__ */ jsxs("div", {
8932
+ className: "dy-flex dy-flex-col dy-gap-3 dy-max-h-[400px] dy-overflow-y-auto",
8933
+ children: [
8934
+ /* @__PURE__ */ jsxs("div", {
8935
+ className: "dy-text-xs dy-font-semibold dy-text-muted-foreground",
8936
+ children: [
8937
+ field.label || field.name,
8938
+ " (",
8939
+ items.length,
8940
+ " items)"
8941
+ ]
8942
+ }),
8943
+ items.map((item, idx) => /* @__PURE__ */ jsxs("div", {
8944
+ className: "dy-border dy-rounded-md dy-p-2 dy-flex dy-flex-col dy-gap-2",
8945
+ children: [/* @__PURE__ */ jsxs("div", {
8946
+ className: "dy-flex dy-items-center dy-justify-between",
8947
+ children: [/* @__PURE__ */ jsxs("span", {
8948
+ className: "dy-text-[10px] dy-font-medium dy-text-muted-foreground",
8949
+ children: ["Item ", idx + 1]
8950
+ }), /* @__PURE__ */ jsx("button", {
8951
+ type: "button",
8952
+ className: "dy-text-[10px] dy-text-destructive hover:dy-underline",
8953
+ onClick: () => onChange(items.filter((_, i) => i !== idx)),
8954
+ children: "Remove"
8955
+ })]
8956
+ }), subFields.filter((sf) => ![
8957
+ "object",
8958
+ "array",
8959
+ "blocks",
8960
+ "join",
8961
+ "row",
8962
+ "richText"
8963
+ ].includes(sf.type)).map((sf) => /* @__PURE__ */ jsx(SubFieldRow, {
8964
+ subField: sf,
8965
+ value: item[sf.name],
8966
+ onChange: (val) => updateItem(idx, sf.name, val),
8967
+ collection
8968
+ }, sf.name))]
8969
+ }, idx)),
8970
+ /* @__PURE__ */ jsx("button", {
8971
+ type: "button",
8972
+ className: "dy-w-full dy-border dy-border-dashed dy-rounded dy-py-1.5 dy-text-xs dy-text-muted-foreground hover:dy-bg-muted/30 dy-transition-colors",
8973
+ onClick: () => onChange([...items, {}]),
8974
+ children: "+ Add item"
8975
+ })
8976
+ ]
8977
+ });
8978
+ }
8873
8979
  function SpreadsheetEditor({ slug, schema, data, onSave, isSaving }) {
8874
8980
  const { client } = useDyrected();
8875
8981
  const [drafts, setDrafts] = React$1.useState({});
@@ -8960,7 +9066,7 @@ function SpreadsheetEditor({ slug, schema, data, onSave, isSaving }) {
8960
9066
  };
8961
9067
  const columns = React$1.useMemo(() => {
8962
9068
  const cols = [];
8963
- schema.fields.filter((f) => f.name !== "password" && !f.admin?.hidden && f.type !== "row" && f.type !== "join").forEach((field) => {
9069
+ schema.fields.filter((f) => f.name !== "password" && !f.admin?.hidden && f.type !== "row" && f.type !== "join" && f.type !== "blocks").forEach((field) => {
8964
9070
  const isReadOnly = !!field.admin?.readOnly;
8965
9071
  if (field.type === "text" || field.type === "email" || field.type === "url") {
8966
9072
  cols.push({
@@ -8978,6 +9084,34 @@ function SpreadsheetEditor({ slug, schema, data, onSave, isSaving }) {
8978
9084
  });
8979
9085
  return;
8980
9086
  }
9087
+ if (field.type === "number") {
9088
+ cols.push({
9089
+ ...keyColumn(field.name, {
9090
+ component: ({ rowData: value, setRowData: onChange, focus }) => {
9091
+ return /* @__PURE__ */ jsx("input", {
9092
+ type: "number",
9093
+ className: "dy-w-full dy-h-full dy-bg-transparent dy-outline-none dy-px-2 dy-text-xs dy-text-foreground [appearance:textfield] [&::-webkit-outer-spin-button]:dy-appearance-none [&::-webkit-inner-spin-button]:dy-appearance-none",
9094
+ value: value != null ? String(value) : "",
9095
+ onChange: (e) => {
9096
+ const raw = e.target.value;
9097
+ onChange(raw === "" ? null : Number(raw));
9098
+ },
9099
+ disabled: isReadOnly,
9100
+ autoFocus: focus
9101
+ });
9102
+ },
9103
+ deleteValue: () => null,
9104
+ copyValue: ({ rowData: value }) => String(value ?? ""),
9105
+ pasteValue: ({ value }) => {
9106
+ const n = Number(value);
9107
+ return isNaN(n) ? null : n;
9108
+ }
9109
+ }),
9110
+ title: field.label || field.name,
9111
+ disabled: isReadOnly
9112
+ });
9113
+ return;
9114
+ }
8981
9115
  if (field.type === "date" || field.type === "datetime" || field.type === "time") {
8982
9116
  const inputType = field.type === "date" ? "date" : field.type === "datetime" ? "datetime-local" : "time";
8983
9117
  cols.push({
@@ -9021,10 +9155,18 @@ function SpreadsheetEditor({ slug, schema, data, onSave, isSaving }) {
9021
9155
  if (!rowData) return null;
9022
9156
  const value = rowData[field.name];
9023
9157
  const isSelectOrRadio = field.type === "select" || field.type === "radio";
9158
+ const isMultiSelect = field.type === "multiSelect";
9159
+ const isArray = field.type === "array";
9160
+ const isObject = field.type === "object";
9024
9161
  let display = "";
9025
- if (!isSelectOrRadio && value !== null && value !== void 0) if (typeof value === "object") display = value.title || value.name || value.filename || JSON.stringify(value);
9162
+ if (!isSelectOrRadio && !isMultiSelect && !isArray && !isObject && value !== null && value !== void 0) if (typeof value === "object") display = value.title || value.name || value.filename || JSON.stringify(value);
9026
9163
  else display = String(value);
9027
9164
  if (field.type === "richText" && display) display = display.replace(/<[^>]*>/g, "");
9165
+ if (isArray) display = Array.isArray(value) ? `${value.length} item${value.length !== 1 ? "s" : ""}` : "0 items";
9166
+ if (isObject && typeof value === "object" && value !== null) {
9167
+ const keys = Object.keys(value).filter((k) => value[k] != null);
9168
+ display = keys.length > 0 ? keys.slice(0, 2).map((k) => `${k}: ${String(value[k]).slice(0, 15)}`).join(", ") : "{ empty }";
9169
+ }
9028
9170
  return /* @__PURE__ */ jsxs("div", {
9029
9171
  tabIndex: -1,
9030
9172
  className: "dy-w-full dy-h-full dy-px-2 dy-py-1 dy-truncate dy-cursor-pointer hover:dy-bg-muted/30 dy-flex dy-items-center dy-gap-2 dy-outline-none",
@@ -9051,6 +9193,14 @@ function SpreadsheetEditor({ slug, schema, data, onSave, isSaving }) {
9051
9193
  collection: slug,
9052
9194
  siblingValues: rowData
9053
9195
  })
9196
+ }) : isMultiSelect ? /* @__PURE__ */ jsx("span", {
9197
+ className: "dy-text-xs dy-text-foreground",
9198
+ children: /* @__PURE__ */ jsx(MultiSelectCellDisplay, {
9199
+ field,
9200
+ value,
9201
+ collection: slug,
9202
+ siblingValues: rowData
9203
+ })
9054
9204
  }) : /* @__PURE__ */ jsx("span", {
9055
9205
  className: "dy-text-xs dy-text-muted-foreground",
9056
9206
  children: display || `Click to edit`
@@ -9170,10 +9320,15 @@ function SpreadsheetEditor({ slug, schema, data, onSave, isSaving }) {
9170
9320
  if (!open) handlePopoverClose();
9171
9321
  },
9172
9322
  children: activePopover && activePopover.field.type !== "richText" && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(PopoverAnchor, { virtualRef: { current: activePopover.element } }), /* @__PURE__ */ jsxs(PopoverContent, {
9173
- className: "dy-z-[100] dy-p-3 dy-w-80 dy-bg-popover dy-border dy-rounded-md dy-shadow-md",
9323
+ className: cn("dy-z-[100] dy-p-3 dy-bg-popover dy-border dy-rounded-md dy-shadow-md", activePopover.field.type === "object" || activePopover.field.type === "array" ? "dy-w-96" : "dy-w-80"),
9174
9324
  onInteractOutside: () => {
9175
9325
  const { rowIdx, field } = activePopover;
9176
- if (field.type !== "textarea" && field.type !== "json") return;
9326
+ if (![
9327
+ "textarea",
9328
+ "json",
9329
+ "object",
9330
+ "array"
9331
+ ].includes(field.type)) return;
9177
9332
  const row = gridData[rowIdx];
9178
9333
  if (row) setDrafts((prev) => ({
9179
9334
  ...prev,
@@ -9258,6 +9413,28 @@ function SpreadsheetEditor({ slug, schema, data, onSave, isSaving }) {
9258
9413
  setLocalDraft(val);
9259
9414
  commitValue(val);
9260
9415
  }
9416
+ }),
9417
+ activePopover.field.type === "multiSelect" && /* @__PURE__ */ jsx(MultiSelectPopoverEditor, {
9418
+ field: activePopover.field,
9419
+ value: Array.isArray(localDraft) ? localDraft : [],
9420
+ collection: slug,
9421
+ siblingValues: gridData[activePopover.rowIdx] || {},
9422
+ onToggle: (val) => {
9423
+ setLocalDraft(val);
9424
+ commitValue(val);
9425
+ }
9426
+ }),
9427
+ activePopover.field.type === "object" && /* @__PURE__ */ jsx(ObjectPopoverEditor, {
9428
+ field: activePopover.field,
9429
+ value: localDraft,
9430
+ onChange: (val) => setLocalDraft(val),
9431
+ collection: slug
9432
+ }),
9433
+ activePopover.field.type === "array" && /* @__PURE__ */ jsx(ArrayPopoverEditor, {
9434
+ field: activePopover.field,
9435
+ value: localDraft,
9436
+ onChange: (val) => setLocalDraft(val),
9437
+ collection: slug
9261
9438
  })
9262
9439
  ]
9263
9440
  })] })
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dyrected/admin",
3
- "version": "2.5.38",
3
+ "version": "2.5.40",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "dist"
@@ -64,8 +64,8 @@
64
64
  "tailwind-merge": "^3.5.0",
65
65
  "tailwindcss-animate": "^1.0.7",
66
66
  "zod": "^3.25.76",
67
- "@dyrected/core": "^2.5.38",
68
- "@dyrected/sdk": "^2.5.38"
67
+ "@dyrected/core": "^2.5.40",
68
+ "@dyrected/sdk": "^2.5.40"
69
69
  },
70
70
  "peerDependencies": {
71
71
  "@tanstack/react-query": "^5.0.0",