@wordrhyme/auto-crud 1.2.4 → 1.3.0

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
@@ -461,10 +461,10 @@ function getColumnVariant(column) {
461
461
  function splitKey(key) {
462
462
  const match = key.match(/^(.*?)(?:__(\d+))?$/);
463
463
  if (!match) return { base: key };
464
- const index = match[2] ? Number(match[2]) : void 0;
464
+ const index$1 = match[2] ? Number(match[2]) : void 0;
465
465
  return {
466
466
  base: match[1] ?? key,
467
- index
467
+ index: index$1
468
468
  };
469
469
  }
470
470
  function isFilterKey(key, columnIds) {
@@ -1256,7 +1256,7 @@ function SortableItem(props) {
1256
1256
  if (value === "") throw new Error(`\`${ITEM_NAME$1}\` value cannot be an empty string`);
1257
1257
  const context = useSortableContext(ITEM_NAME$1);
1258
1258
  const id = React.useId();
1259
- const { attributes, listeners, setNodeRef, setActivatorNodeRef, transform, transition, isDragging } = useSortable({
1259
+ const { attributes, listeners: listeners$1, setNodeRef, setActivatorNodeRef, transform, transition, isDragging } = useSortable({
1260
1260
  id: value,
1261
1261
  disabled
1262
1262
  });
@@ -1279,14 +1279,14 @@ function SortableItem(props) {
1279
1279
  const itemContext = React.useMemo(() => ({
1280
1280
  id,
1281
1281
  attributes,
1282
- listeners,
1282
+ listeners: listeners$1,
1283
1283
  setActivatorNodeRef,
1284
1284
  isDragging,
1285
1285
  disabled
1286
1286
  }), [
1287
1287
  id,
1288
1288
  attributes,
1289
- listeners,
1289
+ listeners$1,
1290
1290
  setActivatorNodeRef,
1291
1291
  isDragging,
1292
1292
  disabled
@@ -1301,7 +1301,7 @@ function SortableItem(props) {
1301
1301
  "data-slot": "sortable-item",
1302
1302
  ...itemProps,
1303
1303
  ...asHandle && !disabled ? attributes : {},
1304
- ...asHandle && !disabled ? listeners : {},
1304
+ ...asHandle && !disabled ? listeners$1 : {},
1305
1305
  ref: composedRef,
1306
1306
  style: composedStyle,
1307
1307
  className: cn("focus-visible:outline-hidden focus-visible:ring-1 focus-visible:ring-ring focus-visible:ring-offset-1", {
@@ -1528,9 +1528,9 @@ function DataTableFilterList({ table, debounceMs = DEBOUNCE_MS$2, throttleMs = T
1528
1528
  children: /* @__PURE__ */ jsx("div", {
1529
1529
  role: "list",
1530
1530
  className: "flex max-h-[300px] flex-col gap-2 overflow-y-auto p-1",
1531
- children: filters.map((filter, index) => /* @__PURE__ */ jsx(DataTableFilterItem$1, {
1531
+ children: filters.map((filter, index$1) => /* @__PURE__ */ jsx(DataTableFilterItem$1, {
1532
1532
  filter,
1533
- index,
1533
+ index: index$1,
1534
1534
  filterItemId: `${id}-filter-${filter.filterId}`,
1535
1535
  joinOperator,
1536
1536
  setJoinOperator,
@@ -1571,7 +1571,7 @@ function DataTableFilterList({ table, debounceMs = DEBOUNCE_MS$2, throttleMs = T
1571
1571
  }) })]
1572
1572
  });
1573
1573
  }
1574
- function DataTableFilterItem$1({ filter, index, filterItemId, joinOperator, setJoinOperator, columns, onFilterUpdate, onFilterRemove }) {
1574
+ function DataTableFilterItem$1({ filter, index: index$1, filterItemId, joinOperator, setJoinOperator, columns, onFilterUpdate, onFilterRemove }) {
1575
1575
  const [showFieldSelector, setShowFieldSelector] = React.useState(false);
1576
1576
  const [showOperatorSelector, setShowOperatorSelector] = React.useState(false);
1577
1577
  const [showValueSelector, setShowValueSelector] = React.useState(false);
@@ -1609,10 +1609,10 @@ function DataTableFilterItem$1({ filter, index, filterItemId, joinOperator, setJ
1609
1609
  children: [
1610
1610
  /* @__PURE__ */ jsx("div", {
1611
1611
  className: "min-w-[72px] text-center",
1612
- children: index === 0 ? /* @__PURE__ */ jsx("span", {
1612
+ children: index$1 === 0 ? /* @__PURE__ */ jsx("span", {
1613
1613
  className: "text-muted-foreground text-sm",
1614
1614
  children: "Where"
1615
- }) : index === 1 ? /* @__PURE__ */ jsxs(Select, {
1615
+ }) : index$1 === 1 ? /* @__PURE__ */ jsxs(Select, {
1616
1616
  value: joinOperator,
1617
1617
  onValueChange: (value) => setJoinOperator(value),
1618
1618
  children: [/* @__PURE__ */ jsx(SelectTrigger, {
@@ -3045,10 +3045,10 @@ function AutoTableSimpleFilters({ table, shallow = false, filters: externalFilte
3045
3045
  "data-filter-leading": true,
3046
3046
  children: leading
3047
3047
  }) : null,
3048
- filterColumns.map((column, index) => {
3048
+ filterColumns.map((column, index$1) => {
3049
3049
  const meta = column.columnDef.meta;
3050
3050
  const value = getFilterValue(column.id);
3051
- const isHidden = !expanded && visibleCount !== null && index >= visibleCount;
3051
+ const isHidden = !expanded && visibleCount !== null && index$1 >= visibleCount;
3052
3052
  switch (meta.variant) {
3053
3053
  case "text": return /* @__PURE__ */ jsx("div", {
3054
3054
  className: isHidden ? "hidden" : "",
@@ -3460,7 +3460,7 @@ function focusFirst(candidates, preventScroll = false) {
3460
3460
  }
3461
3461
  }
3462
3462
  function wrapArray(array, startIndex) {
3463
- return array.map((_, index) => array[(startIndex + index) % array.length]);
3463
+ return array.map((_, index$1) => array[(startIndex + index$1) % array.length]);
3464
3464
  }
3465
3465
  function getDirectionAwareKey(key, dir) {
3466
3466
  if (dir !== "rtl") return key;
@@ -4003,9 +4003,9 @@ function parseZodField(schema) {
4003
4003
  if (def?.type === "enum") {
4004
4004
  let enumValues$1;
4005
4005
  if (def?.entries) {
4006
- const entries = def.entries;
4007
- if (Array.isArray(entries)) enumValues$1 = entries.map(String);
4008
- else if (typeof entries === "object") enumValues$1 = Object.keys(entries);
4006
+ const entries$1 = def.entries;
4007
+ if (Array.isArray(entries$1)) enumValues$1 = entries$1.map(String);
4008
+ else if (typeof entries$1 === "object") enumValues$1 = Object.keys(entries$1);
4009
4009
  } else if (def?.values) {
4010
4010
  const values = def.values;
4011
4011
  if (Array.isArray(values)) enumValues$1 = values.map(String);
@@ -4026,9 +4026,9 @@ function parseZodField(schema) {
4026
4026
  });
4027
4027
  let enumValues;
4028
4028
  if (def?.entries) {
4029
- const entries = def.entries;
4030
- if (Array.isArray(entries)) enumValues = entries.map(String);
4031
- else if (typeof entries === "object") enumValues = Object.keys(entries);
4029
+ const entries$1 = def.entries;
4030
+ if (Array.isArray(entries$1)) enumValues = entries$1.map(String);
4031
+ else if (typeof entries$1 === "object") enumValues = Object.keys(entries$1);
4032
4032
  } else if (def?.values) {
4033
4033
  const values = def.values;
4034
4034
  if (Array.isArray(values)) enumValues = values.map(String);
@@ -4223,6 +4223,10 @@ function createSelectColumn() {
4223
4223
  * 创建操作列
4224
4224
  */
4225
4225
  function createActionsColumn(items) {
4226
+ const renderActionComponent = (item, row) => {
4227
+ if (!item.component) return null;
4228
+ return typeof item.component === "function" ? item.component(item.getContext?.(row.original)) : item.component;
4229
+ };
4226
4230
  return {
4227
4231
  id: "actions",
4228
4232
  cell: ({ row }) => /* @__PURE__ */ jsxs(DropdownMenu, { children: [/* @__PURE__ */ jsx(DropdownMenuTrigger, {
@@ -4236,11 +4240,11 @@ function createActionsColumn(items) {
4236
4240
  }), /* @__PURE__ */ jsx(DropdownMenuContent, {
4237
4241
  align: "end",
4238
4242
  className: "w-40",
4239
- children: items.map((item, i) => /* @__PURE__ */ jsxs(React.Fragment, { children: [item.separator && /* @__PURE__ */ jsx(DropdownMenuSeparator, {}), /* @__PURE__ */ jsx(DropdownMenuItem, {
4243
+ children: items.map((item, i) => /* @__PURE__ */ jsxs(React.Fragment, { children: [item.separator && /* @__PURE__ */ jsx(DropdownMenuSeparator, {}), item.component ? renderActionComponent(item, row) : item.label && item.onClick ? /* @__PURE__ */ jsx(DropdownMenuItem, {
4240
4244
  className: item.variant === "destructive" ? "text-destructive" : "",
4241
- onClick: () => item.onClick(row.original),
4245
+ onClick: () => item.onClick?.(row.original),
4242
4246
  children: item.label
4243
- })] }, i))
4247
+ }) : null] }, i))
4244
4248
  })] }),
4245
4249
  size: 40
4246
4250
  };
@@ -4541,34 +4545,36 @@ function AutoTableActionBar({ table, onDeleteSelected, onUpdateSelected, batchUp
4541
4545
  renderComponent,
4542
4546
  selectedRows
4543
4547
  ]);
4544
- const renderCustomAction = React.useCallback((action, index) => {
4545
- if (action.component) return /* @__PURE__ */ jsx(React.Fragment, { children: renderComponent(action.component) }, `custom-${index}`);
4548
+ const renderCustomAction = React.useCallback((action, index$1) => {
4549
+ if (action.component) return /* @__PURE__ */ jsx(React.Fragment, { children: renderComponent(action.component) }, `custom-${index$1}`);
4546
4550
  if (!action.label || !action.onClick) return null;
4547
4551
  return /* @__PURE__ */ jsx(ActionBarItem, {
4548
4552
  variant: action.variant === "destructive" ? "destructive" : "secondary",
4549
4553
  onClick: (event) => action.onClick?.(selectedRows, actionContext, event),
4550
4554
  children: action.label
4551
- }, `custom-${index}`);
4555
+ }, `custom-${index$1}`);
4552
4556
  }, [
4553
4557
  actionContext,
4554
4558
  renderComponent,
4555
4559
  selectedRows
4556
4560
  ]);
4557
4561
  const renderedActions = React.useMemo(() => {
4558
- const resolvedActions = typeof actions === "function" ? actions([
4562
+ const configuredActions = typeof actions === "function" ? actions([
4559
4563
  { type: "batchUpdate" },
4560
4564
  { type: "export" },
4561
4565
  { type: "delete" }
4562
4566
  ]) : actions;
4563
- if (!resolvedActions || resolvedActions.length === 0) return /* @__PURE__ */ jsxs(Fragment, { children: [
4567
+ const resolvedActions = configuredActions?.filter((action) => !action.hidden);
4568
+ if (configuredActions === void 0) return /* @__PURE__ */ jsxs(Fragment, { children: [
4564
4569
  renderBuiltinAction({ type: "batchUpdate" }),
4565
4570
  extraActions,
4566
4571
  showDefaultExport && renderBuiltinAction({ type: "export" }),
4567
4572
  renderBuiltinAction({ type: "delete" })
4568
4573
  ] });
4574
+ if (!resolvedActions || resolvedActions.length === 0) return null;
4569
4575
  if (!resolvedActions.some((action) => action.type !== "custom")) {
4570
- const startNodes = resolvedActions.filter(isBatchCustomAction).filter((action) => action.position === "start").map((action, index) => renderCustomAction(action, index));
4571
- const endNodes = resolvedActions.filter(isBatchCustomAction).filter((action) => action.position !== "start").map((action, index) => renderCustomAction(action, index));
4576
+ const startNodes = resolvedActions.filter(isBatchCustomAction).filter((action) => action.position === "start").map((action, index$1) => renderCustomAction(action, index$1));
4577
+ const endNodes = resolvedActions.filter(isBatchCustomAction).filter((action) => action.position !== "start").map((action, index$1) => renderCustomAction(action, index$1));
4572
4578
  return /* @__PURE__ */ jsxs(Fragment, { children: [
4573
4579
  startNodes,
4574
4580
  renderBuiltinAction({ type: "batchUpdate" }),
@@ -4578,8 +4584,8 @@ function AutoTableActionBar({ table, onDeleteSelected, onUpdateSelected, batchUp
4578
4584
  endNodes
4579
4585
  ] });
4580
4586
  }
4581
- return resolvedActions.map((action, index) => {
4582
- if (action.type === "custom") return renderCustomAction(action, index);
4587
+ return resolvedActions.map((action, index$1) => {
4588
+ if (action.type === "custom") return renderCustomAction(action, index$1);
4583
4589
  return /* @__PURE__ */ jsx(React.Fragment, { children: renderBuiltinAction(action) }, action.type);
4584
4590
  });
4585
4591
  }, [
@@ -5201,39 +5207,39 @@ function createEditFormSchema(schema, options) {
5201
5207
  //#endregion
5202
5208
  //#region src/lib/registries.ts
5203
5209
  function createRegistry(label) {
5204
- const entries = /* @__PURE__ */ new Map();
5205
- const listeners = /* @__PURE__ */ new Set();
5206
- let notifyScheduled = false;
5207
- const notify = () => {
5208
- if (notifyScheduled) return;
5209
- notifyScheduled = true;
5210
+ const entries$1 = /* @__PURE__ */ new Map();
5211
+ const listeners$1 = /* @__PURE__ */ new Set();
5212
+ let notifyScheduled$1 = false;
5213
+ const notify$1 = () => {
5214
+ if (notifyScheduled$1) return;
5215
+ notifyScheduled$1 = true;
5210
5216
  queueMicrotask(() => {
5211
- notifyScheduled = false;
5212
- for (const listener of listeners) listener();
5217
+ notifyScheduled$1 = false;
5218
+ for (const listener of listeners$1) listener();
5213
5219
  });
5214
5220
  };
5215
5221
  return {
5216
5222
  register(name, value) {
5217
- if (entries.has(name)) {
5223
+ if (entries$1.has(name)) {
5218
5224
  console.warn(`[AutoCrud] ${label} "${name}" is already registered.`);
5219
5225
  return;
5220
5226
  }
5221
- entries.set(name, value);
5222
- notify();
5227
+ entries$1.set(name, value);
5228
+ notify$1();
5223
5229
  },
5224
5230
  unregister(name) {
5225
- if (!entries.delete(name)) return;
5226
- notify();
5231
+ if (!entries$1.delete(name)) return;
5232
+ notify$1();
5227
5233
  },
5228
5234
  get(name) {
5229
- return entries.get(name);
5235
+ return entries$1.get(name);
5230
5236
  },
5231
5237
  all() {
5232
- return Object.fromEntries(entries);
5238
+ return Object.fromEntries(entries$1);
5233
5239
  },
5234
5240
  subscribe(listener) {
5235
- listeners.add(listener);
5236
- return () => listeners.delete(listener);
5241
+ listeners$1.add(listener);
5242
+ return () => listeners$1.delete(listener);
5237
5243
  }
5238
5244
  };
5239
5245
  }
@@ -5324,9 +5330,9 @@ const defaultFieldComponents = {
5324
5330
  }
5325
5331
  };
5326
5332
  function useRegistryVersion$1(subscribe) {
5327
- const [version, setVersion] = useState(0);
5333
+ const [version$1, setVersion] = useState(0);
5328
5334
  useEffect(() => subscribe(() => setVersion((current) => current + 1)), [subscribe]);
5329
- return version;
5335
+ return version$1;
5330
5336
  }
5331
5337
  function isDataSourceConfig(value) {
5332
5338
  return typeof value === "string" && value.length > 0;
@@ -5357,8 +5363,8 @@ function useFormDataSources(form, schema) {
5357
5363
  const loadVersions = /* @__PURE__ */ new Map();
5358
5364
  const controller = typeof AbortController === "undefined" ? void 0 : new AbortController();
5359
5365
  const loadFieldOptions = async (fieldName, source) => {
5360
- const version = (loadVersions.get(fieldName) ?? 0) + 1;
5361
- loadVersions.set(fieldName, version);
5366
+ const version$1 = (loadVersions.get(fieldName) ?? 0) + 1;
5367
+ loadVersions.set(fieldName, version$1);
5362
5368
  const entry = dataSources.get(source.key);
5363
5369
  if (!entry) {
5364
5370
  form.setFieldState(fieldName, (state) => {
@@ -5372,7 +5378,7 @@ function useFormDataSources(form, schema) {
5372
5378
  values: Object.fromEntries(entry.dependencies.map((dependency) => [dependency, form.getValuesIn(dependency)])),
5373
5379
  signal: controller?.signal
5374
5380
  }));
5375
- if (!active || loadVersions.get(fieldName) !== version) return;
5381
+ if (!active || loadVersions.get(fieldName) !== version$1) return;
5376
5382
  form.setFieldState(fieldName, (state) => {
5377
5383
  state.dataSource = options;
5378
5384
  });
@@ -6241,10 +6247,157 @@ function ImportDialog({ open, onOpenChange, onImport, columns = [], title, local
6241
6247
  });
6242
6248
  }
6243
6249
 
6250
+ //#endregion
6251
+ //#region src/lib/crud-actions.ts
6252
+ const entries = /* @__PURE__ */ new Map();
6253
+ const index = /* @__PURE__ */ new Map();
6254
+ const listeners = /* @__PURE__ */ new Set();
6255
+ let seq = 0;
6256
+ let version = 0;
6257
+ let notifyScheduled = false;
6258
+ function actionKey(registration, action, index$1) {
6259
+ return [
6260
+ registration.ownerId,
6261
+ registration.targetId,
6262
+ registration.zone,
6263
+ action.id ?? action.type,
6264
+ index$1
6265
+ ].join(":");
6266
+ }
6267
+ function indexKey(targetId, zone) {
6268
+ return `${targetId}:${zone}`;
6269
+ }
6270
+ function addToIndex(key, entry) {
6271
+ const bucketKey = indexKey(entry.targetId, entry.zone);
6272
+ const bucket = index.get(bucketKey) ?? /* @__PURE__ */ new Set();
6273
+ bucket.add(key);
6274
+ index.set(bucketKey, bucket);
6275
+ }
6276
+ function removeFromIndex(key, entry) {
6277
+ const bucketKey = indexKey(entry.targetId, entry.zone);
6278
+ const bucket = index.get(bucketKey);
6279
+ if (!bucket) return;
6280
+ bucket.delete(key);
6281
+ if (bucket.size === 0) index.delete(bucketKey);
6282
+ }
6283
+ function notify() {
6284
+ version += 1;
6285
+ if (notifyScheduled) return;
6286
+ notifyScheduled = true;
6287
+ queueMicrotask(() => {
6288
+ notifyScheduled = false;
6289
+ for (const listener of listeners) listener();
6290
+ });
6291
+ }
6292
+ function unregisterOwnerActions(ownerId, options = {}, shouldNotify = true) {
6293
+ let changed = false;
6294
+ for (const [key, entry] of entries) {
6295
+ if (entry.ownerId !== ownerId) continue;
6296
+ if (options.targetId && entry.targetId !== options.targetId) continue;
6297
+ if (options.zone && entry.zone !== options.zone) continue;
6298
+ entries.delete(key);
6299
+ removeFromIndex(key, entry);
6300
+ changed = true;
6301
+ }
6302
+ if (changed && shouldNotify) notify();
6303
+ }
6304
+ function sortEntries(left, right) {
6305
+ const orderDiff = left.order - right.order;
6306
+ if (orderDiff !== 0) return orderDiff;
6307
+ const ownerDiff = left.ownerId.localeCompare(right.ownerId);
6308
+ if (ownerDiff !== 0) return ownerDiff;
6309
+ return left.seq - right.seq;
6310
+ }
6311
+ function withoutRegistryMeta(action) {
6312
+ const { id, order,...rest } = action;
6313
+ return rest;
6314
+ }
6315
+ function isCustomAction$1(action) {
6316
+ return action.type === "custom";
6317
+ }
6318
+ function resolveActions$1(targetId, zone, ownerActions) {
6319
+ const baseActions = ownerActions.filter((action) => !action.hidden).map((action) => withoutRegistryMeta(action));
6320
+ if (!targetId) return baseActions;
6321
+ const registered = crudActions.get(targetId, zone);
6322
+ if (registered.length === 0) return baseActions;
6323
+ const nextActions = [...baseActions];
6324
+ const startCustomActions = [];
6325
+ const endCustomActions = [];
6326
+ for (const entry of registered) {
6327
+ const action = entry.action;
6328
+ if (action.hidden && isCustomAction$1(action)) continue;
6329
+ if (isCustomAction$1(action)) {
6330
+ const custom = withoutRegistryMeta(action);
6331
+ if (custom.position === "start") startCustomActions.push(custom);
6332
+ else endCustomActions.push(custom);
6333
+ continue;
6334
+ }
6335
+ const existingIndex = nextActions.findIndex((item) => item.type === action.type);
6336
+ if (action.hidden) {
6337
+ if (existingIndex >= 0) nextActions.splice(existingIndex, 1);
6338
+ continue;
6339
+ }
6340
+ const builtin = withoutRegistryMeta(action);
6341
+ if (existingIndex >= 0) nextActions[existingIndex] = {
6342
+ ...nextActions[existingIndex],
6343
+ ...builtin
6344
+ };
6345
+ else nextActions.push(builtin);
6346
+ }
6347
+ return [
6348
+ ...startCustomActions,
6349
+ ...nextActions,
6350
+ ...endCustomActions
6351
+ ];
6352
+ }
6353
+ const crudActions = {
6354
+ register(registration) {
6355
+ unregisterOwnerActions(registration.ownerId, {
6356
+ targetId: registration.targetId,
6357
+ zone: registration.zone
6358
+ }, false);
6359
+ registration.actions.forEach((action, index$1) => {
6360
+ const key = actionKey(registration, action, index$1);
6361
+ const entry = {
6362
+ targetId: registration.targetId,
6363
+ zone: registration.zone,
6364
+ ownerId: registration.ownerId,
6365
+ action,
6366
+ order: action.order ?? 100,
6367
+ seq: seq++
6368
+ };
6369
+ entries.set(key, entry);
6370
+ addToIndex(key, entry);
6371
+ });
6372
+ notify();
6373
+ },
6374
+ unregister: unregisterOwnerActions,
6375
+ clear() {
6376
+ if (entries.size === 0) return;
6377
+ entries.clear();
6378
+ index.clear();
6379
+ notify();
6380
+ },
6381
+ get(targetId, zone) {
6382
+ return Array.from(index.get(indexKey(targetId, zone)) ?? []).flatMap((key) => {
6383
+ const entry = entries.get(key);
6384
+ return entry ? [entry] : [];
6385
+ }).sort(sortEntries);
6386
+ },
6387
+ resolve: resolveActions$1,
6388
+ subscribe(listener) {
6389
+ listeners.add(listener);
6390
+ return () => listeners.delete(listener);
6391
+ },
6392
+ getSnapshot() {
6393
+ return version;
6394
+ }
6395
+ };
6396
+
6244
6397
  //#endregion
6245
6398
  //#region src/components/auto-crud/auto-crud-table.tsx
6246
6399
  let autoCrudToolbarResolver = null;
6247
- function setAutoCrudToolbarResolver(resolver) {
6400
+ function setToolbarResolver(resolver) {
6248
6401
  autoCrudToolbarResolver = resolver;
6249
6402
  }
6250
6403
  function getDefaultToolbarActions() {
@@ -6254,25 +6407,64 @@ function getDefaultToolbarActions() {
6254
6407
  { type: "create" }
6255
6408
  ];
6256
6409
  }
6410
+ function getDefaultRowActions() {
6411
+ return [
6412
+ { type: "view" },
6413
+ { type: "edit" },
6414
+ { type: "copy" },
6415
+ { type: "delete" }
6416
+ ];
6417
+ }
6418
+ function getDefaultBatchActions() {
6419
+ return [{ type: "batchUpdate" }, { type: "delete" }];
6420
+ }
6421
+ function isCustomAction(action) {
6422
+ return action.type === "custom";
6423
+ }
6424
+ function resolveOwnerActions(config, defaults) {
6425
+ const resolved = typeof config === "function" ? config([...defaults]) : config;
6426
+ const items = resolved !== void 0 && resolved.length > 0 ? [...resolved] : [];
6427
+ if (items.length === 0) return [...defaults];
6428
+ if (items.some((item) => !isCustomAction(item))) return items;
6429
+ const startItems = items.filter((item) => isCustomAction(item) && item.position === "start");
6430
+ const endItems = items.filter((item) => isCustomAction(item) && item.position !== "start");
6431
+ return [
6432
+ ...startItems,
6433
+ ...defaults,
6434
+ ...endItems
6435
+ ];
6436
+ }
6257
6437
  function resolveOwnerToolbarActions(toolbar) {
6258
- const resolved = typeof toolbar === "function" ? toolbar(getDefaultToolbarActions()) : toolbar;
6259
- return resolved !== void 0 && resolved.length > 0 ? [...resolved] : getDefaultToolbarActions();
6438
+ return resolveOwnerActions(toolbar, getDefaultToolbarActions());
6439
+ }
6440
+ function resolveOwnerRowActions(actions) {
6441
+ return resolveOwnerActions(actions, getDefaultRowActions());
6442
+ }
6443
+ function resolveOwnerBatchActions(actions) {
6444
+ return resolveOwnerActions(actions, getDefaultBatchActions());
6445
+ }
6446
+ function isUnifiedActionsConfig(actions) {
6447
+ return typeof actions === "object" && actions !== null && !Array.isArray(actions);
6448
+ }
6449
+ function readRowId(row, idKey) {
6450
+ const value = row[idKey];
6451
+ return value === null || value === void 0 ? void 0 : String(value);
6260
6452
  }
6261
6453
  function readRowIds(rows, idKey) {
6262
6454
  return rows.flatMap((row) => {
6263
- const value = row[idKey];
6264
- return value === null || value === void 0 ? [] : [String(value)];
6455
+ const value = readRowId(row, idKey);
6456
+ return value === void 0 ? [] : [value];
6265
6457
  });
6266
6458
  }
6267
6459
  function areStringArraysEqual(left, right) {
6268
- return left.length === right.length && left.every((value, index) => value === right[index]);
6460
+ return left.length === right.length && left.every((value, index$1) => value === right[index$1]);
6269
6461
  }
6270
6462
  function haveSameRowSelection(left, right, idKey) {
6271
6463
  if (left.length !== right.length) return false;
6272
6464
  const leftIds = readRowIds(left, idKey);
6273
6465
  const rightIds = readRowIds(right, idKey);
6274
6466
  if (leftIds.length === left.length && rightIds.length === right.length) return areStringArraysEqual(leftIds, rightIds);
6275
- return left.every((row, index) => row === right[index]);
6467
+ return left.every((row, index$1) => row === right[index$1]);
6276
6468
  }
6277
6469
  function readCreateAction(actions) {
6278
6470
  return actions.find((action) => action.type === "create");
@@ -6341,9 +6533,12 @@ function shouldLoadDynamicFilterOptions(config) {
6341
6533
  return normalizeDataSourceConfig(getFilterDataSource(config)) !== void 0;
6342
6534
  }
6343
6535
  function useRegistryVersion(subscribe) {
6344
- const [version, setVersion] = React.useState(0);
6536
+ const [version$1, setVersion] = React.useState(0);
6345
6537
  React.useEffect(() => subscribe(() => setVersion((current) => current + 1)), [subscribe]);
6346
- return version;
6538
+ return version$1;
6539
+ }
6540
+ function useCrudActionsVersion() {
6541
+ return React.useSyncExternalStore(crudActions.subscribe, crudActions.getSnapshot, crudActions.getSnapshot);
6347
6542
  }
6348
6543
  function useDynamicFilterOptions(fields) {
6349
6544
  const registryVersion = useRegistryVersion(dataSources.subscribe);
@@ -6379,9 +6574,9 @@ function useDynamicFilterOptions(fields) {
6379
6574
  if (!controller?.signal.aborted) console.warn(`[AutoCrud] Failed to load filter data source "${source.key}".`, error);
6380
6575
  return [field, []];
6381
6576
  }
6382
- })).then((entries) => {
6577
+ })).then((entries$1) => {
6383
6578
  if (!active) return;
6384
- setOptionsByField(Object.fromEntries(entries));
6579
+ setOptionsByField(Object.fromEntries(entries$1));
6385
6580
  });
6386
6581
  return () => {
6387
6582
  active = false;
@@ -6648,13 +6843,23 @@ function ViewModal({ open, onOpenChange, variant, data, schema, fields: fieldCon
6648
6843
  /**
6649
6844
  * 解析列表行操作
6650
6845
  */
6651
- function resolveActions(actionsOrFn, defaults, rowActionsLocale) {
6652
- const items = typeof actionsOrFn === "function" ? actionsOrFn([
6846
+ function resolveActions(actionsOrFn, defaults, rowActionsLocale, context) {
6847
+ const resolvedItems = typeof actionsOrFn === "function" ? actionsOrFn([
6653
6848
  { type: "view" },
6654
6849
  { type: "edit" },
6655
6850
  { type: "copy" },
6656
6851
  { type: "delete" }
6657
6852
  ]) : actionsOrFn;
6853
+ const getContext = (row) => ({
6854
+ crudId: context.crudId,
6855
+ idKey: context.idKey,
6856
+ row,
6857
+ rowId: readRowId(row, context.idKey),
6858
+ openView: defaults.openView,
6859
+ ...defaults.openEdit ? { openEdit: defaults.openEdit } : {},
6860
+ ...defaults.copyRow ? { copyRow: defaults.copyRow } : {},
6861
+ ...defaults.openDelete ? { openDelete: defaults.openDelete } : {}
6862
+ });
6658
6863
  const defaultItems = [
6659
6864
  {
6660
6865
  label: rowActionsLocale.view,
@@ -6675,17 +6880,23 @@ function resolveActions(actionsOrFn, defaults, rowActionsLocale) {
6675
6880
  variant: "destructive"
6676
6881
  }] : []
6677
6882
  ];
6678
- if (!items || items.length === 0) return defaultItems;
6883
+ if (resolvedItems === void 0) return defaultItems;
6884
+ const items = resolvedItems.filter((item) => !item.hidden);
6885
+ if (items.length === 0) return [];
6679
6886
  if (!items.some((i) => i.type !== "custom")) {
6680
- const startItems = items.filter((i) => i.type === "custom" && i.position === "start").map(({ label, onClick, separator, variant }) => ({
6887
+ const startItems = items.filter((i) => i.type === "custom" && i.position === "start").map(({ label, onClick, component, separator, variant }) => ({
6681
6888
  label,
6682
6889
  onClick,
6890
+ component,
6891
+ getContext,
6683
6892
  separator,
6684
6893
  variant
6685
6894
  }));
6686
- const endItems = items.filter((i) => i.type === "custom" && i.position !== "start").map(({ label, onClick, separator, variant }) => ({
6895
+ const endItems = items.filter((i) => i.type === "custom" && i.position !== "start").map(({ label, onClick, component, separator, variant }) => ({
6687
6896
  label,
6688
6897
  onClick,
6898
+ component,
6899
+ getContext,
6689
6900
  separator,
6690
6901
  variant
6691
6902
  }));
@@ -6712,6 +6923,8 @@ function resolveActions(actionsOrFn, defaults, rowActionsLocale) {
6712
6923
  if (item.type === "custom") return [{
6713
6924
  label: item.label,
6714
6925
  onClick: item.onClick,
6926
+ component: item.component,
6927
+ getContext,
6715
6928
  separator: item.separator,
6716
6929
  variant: item.variant
6717
6930
  }];
@@ -6751,7 +6964,32 @@ function AutoCrudTable({ id, title, description, schema, resource, fields, table
6751
6964
  const getSelectedRowsRef = React.useRef(null);
6752
6965
  const dynamicFilterOptions = useDynamicFilterOptions(resolvedFields);
6753
6966
  const resourceIdKey = resource.idKey ?? "id";
6754
- const ownerToolbarActions = React.useMemo(() => resolveOwnerToolbarActions(toolbar ?? toolbarActions), [toolbar, toolbarActions]);
6967
+ const actionRegistryVersion = useCrudActionsVersion();
6968
+ const unifiedActions = React.useMemo(() => {
6969
+ if (isUnifiedActionsConfig(actionItems)) return actionItems;
6970
+ return { row: actionItems };
6971
+ }, [actionItems]);
6972
+ const ownerToolbarConfig = unifiedActions.toolbar ?? toolbar ?? toolbarActions;
6973
+ const ownerRowConfig = unifiedActions.row;
6974
+ const ownerBatchConfig = unifiedActions.batch ?? tableConfig?.batchActions;
6975
+ const ownerToolbarActions = React.useMemo(() => resolveOwnerToolbarActions(ownerToolbarConfig), [ownerToolbarConfig]);
6976
+ const ownerRowActions = React.useMemo(() => resolveOwnerRowActions(ownerRowConfig), [ownerRowConfig]);
6977
+ const ownerBatchActions = React.useMemo(() => resolveOwnerBatchActions(ownerBatchConfig), [ownerBatchConfig]);
6978
+ const registryToolbarActions = React.useMemo(() => crudActions.resolve(id, "toolbar", ownerToolbarActions), [
6979
+ id,
6980
+ ownerToolbarActions,
6981
+ actionRegistryVersion
6982
+ ]);
6983
+ const registryRowActions = React.useMemo(() => crudActions.resolve(id, "row", ownerRowActions), [
6984
+ id,
6985
+ ownerRowActions,
6986
+ actionRegistryVersion
6987
+ ]);
6988
+ const registryBatchActions = React.useMemo(() => crudActions.resolve(id, "batch", ownerBatchActions), [
6989
+ id,
6990
+ ownerBatchActions,
6991
+ actionRegistryVersion
6992
+ ]);
6755
6993
  const selectedCount = selectedRows.length;
6756
6994
  const selectedRowIds = React.useMemo(() => readRowIds(selectedRows, resourceIdKey), [selectedRows, resourceIdKey]);
6757
6995
  const rowIds = React.useMemo(() => readRowIds(resource.tableData.data, resourceIdKey), [resource.tableData.data, resourceIdKey]);
@@ -6760,7 +6998,7 @@ function AutoCrudTable({ id, title, description, schema, resource, fields, table
6760
6998
  }, [resourceIdKey]);
6761
6999
  const ownerCreateAction = React.useMemo(() => readCreateAction(ownerToolbarActions), [ownerToolbarActions]);
6762
7000
  const toolbarOpenCreate = can.create ? ownerCreateAction?.onClick ?? onCreate ?? resource.handlers.openCreate : void 0;
6763
- const resolvedToolbarActions = resolveToolbarActionsWithResolver(id, ownerToolbarActions, React.useMemo(() => ({
7001
+ const toolbarContext = React.useMemo(() => ({
6764
7002
  crudId: id ?? "",
6765
7003
  idKey: resourceIdKey,
6766
7004
  rowIds,
@@ -6774,7 +7012,8 @@ function AutoCrudTable({ id, title, description, schema, resource, fields, table
6774
7012
  selectedRowIds,
6775
7013
  selectedCount,
6776
7014
  toolbarOpenCreate
6777
- ]));
7015
+ ]);
7016
+ const resolvedToolbarActions = resolveToolbarActionsWithResolver(id, registryToolbarActions, toolbarContext);
6778
7017
  const importColumns = React.useMemo(() => {
6779
7018
  const shape = resolvedSchema.shape;
6780
7019
  const denySet = new Set(denyFields ?? []);
@@ -6844,6 +7083,30 @@ function AutoCrudTable({ id, title, description, schema, resource, fields, table
6844
7083
  resource.modal.copySource,
6845
7084
  resource.modal.selected
6846
7085
  ]);
7086
+ const rowActionDefaults = React.useMemo(() => ({
7087
+ openView: resource.handlers.openView,
7088
+ openEdit: can.update ? resource.handlers.openEdit : void 0,
7089
+ copyRow: can.create ? resource.handlers.copyRow : void 0,
7090
+ openDelete: can.delete ? resource.handlers.openDelete : void 0
7091
+ }), [
7092
+ can.create,
7093
+ can.delete,
7094
+ can.update,
7095
+ resource.handlers.copyRow,
7096
+ resource.handlers.openDelete,
7097
+ resource.handlers.openEdit,
7098
+ resource.handlers.openView
7099
+ ]);
7100
+ const tableRowActions = React.useMemo(() => resolveActions(registryRowActions, rowActionDefaults, locale.rowActions, {
7101
+ crudId: id ?? "",
7102
+ idKey: resourceIdKey
7103
+ }), [
7104
+ id,
7105
+ locale.rowActions,
7106
+ registryRowActions,
7107
+ resourceIdKey,
7108
+ rowActionDefaults
7109
+ ]);
6847
7110
  return /* @__PURE__ */ jsxs("div", {
6848
7111
  className: "space-y-4",
6849
7112
  children: [
@@ -6880,17 +7143,11 @@ function AutoCrudTable({ id, title, description, schema, resource, fields, table
6880
7143
  }, "create");
6881
7144
  return null;
6882
7145
  };
6883
- if (!resolvedToolbarActions || resolvedToolbarActions.length === 0) return /* @__PURE__ */ jsxs("div", {
6884
- className: "flex items-center gap-2",
6885
- children: [
6886
- renderBuiltinButton("import"),
6887
- renderBuiltinButton("export"),
6888
- renderBuiltinButton("create")
6889
- ]
6890
- });
7146
+ const renderToolbarComponent = (component) => typeof component === "function" ? component(toolbarContext) : component;
7147
+ if (!resolvedToolbarActions || resolvedToolbarActions.length === 0) return /* @__PURE__ */ jsx("div", { className: "flex items-center gap-2" });
6891
7148
  if (!resolvedToolbarActions.some((i) => i.type !== "custom")) {
6892
- const startNodes = resolvedToolbarActions.filter((i) => i.type === "custom" && i.position === "start").map((a, i) => /* @__PURE__ */ jsx(React.Fragment, { children: a.component }, `start-${i}`));
6893
- const endNodes = resolvedToolbarActions.filter((i) => i.type === "custom" && i.position !== "start").map((a, i) => /* @__PURE__ */ jsx(React.Fragment, { children: a.component }, `end-${i}`));
7149
+ const startNodes = resolvedToolbarActions.filter((i) => i.type === "custom" && i.position === "start").map((a, i) => /* @__PURE__ */ jsx(React.Fragment, { children: renderToolbarComponent(a.component) }, `start-${i}`));
7150
+ const endNodes = resolvedToolbarActions.filter((i) => i.type === "custom" && i.position !== "start").map((a, i) => /* @__PURE__ */ jsx(React.Fragment, { children: renderToolbarComponent(a.component) }, `end-${i}`));
6894
7151
  return /* @__PURE__ */ jsxs("div", {
6895
7152
  className: "flex items-center gap-2",
6896
7153
  children: [
@@ -6904,10 +7161,10 @@ function AutoCrudTable({ id, title, description, schema, resource, fields, table
6904
7161
  }
6905
7162
  return /* @__PURE__ */ jsx("div", {
6906
7163
  className: "flex items-center gap-2",
6907
- children: resolvedToolbarActions.map((action, index) => {
6908
- if (action.type === "custom") return /* @__PURE__ */ jsx(React.Fragment, { children: action.component }, `custom-${index}`);
7164
+ children: resolvedToolbarActions.map((action, index$1) => {
7165
+ if (action.type === "custom") return /* @__PURE__ */ jsx(React.Fragment, { children: renderToolbarComponent(action.component) }, `custom-${index$1}`);
6909
7166
  if (!(action.type === "import" ? canImport : action.type === "export" ? canExport : can.create)) return null;
6910
- if (action.component) return /* @__PURE__ */ jsx(React.Fragment, { children: action.component }, action.type);
7167
+ if (action.component) return /* @__PURE__ */ jsx(React.Fragment, { children: renderToolbarComponent(action.component) }, action.type);
6911
7168
  return renderBuiltinButton(action.type, {
6912
7169
  onClick: action.onClick,
6913
7170
  label: action.label
@@ -6924,16 +7181,11 @@ function AutoCrudTable({ id, title, description, schema, resource, fields, table
6924
7181
  exclude: hiddenColumns,
6925
7182
  filterMode: tableConfig?.filterModes,
6926
7183
  search: searchConfig,
6927
- actions: resolveActions(actionItems, {
6928
- openView: resource.handlers.openView,
6929
- openEdit: can.update ? resource.handlers.openEdit : void 0,
6930
- copyRow: can.create ? resource.handlers.copyRow : void 0,
6931
- openDelete: can.delete ? resource.handlers.openDelete : void 0
6932
- }, locale.rowActions),
7184
+ actions: tableRowActions,
6933
7185
  onDeleteSelected: can.delete ? resource.handlers.deleteMany : void 0,
6934
7186
  onUpdateSelected: can.update ? resource.handlers.updateMany : void 0,
6935
7187
  batchUpdateFields: can.update ? batchFields : void 0,
6936
- actionBarActions: tableConfig?.batchActions,
7188
+ actionBarActions: registryBatchActions,
6937
7189
  enableExport: canExport,
6938
7190
  showDefaultExport: false,
6939
7191
  initialSorting: tableConfig?.defaultSort,
@@ -8331,19 +8583,19 @@ function createMemoryDataSource(initialData = []) {
8331
8583
  return newItem;
8332
8584
  },
8333
8585
  async update(id, updateData) {
8334
- const index = data.findIndex((item) => item.id === id);
8335
- if (index === -1) throw new Error(`Item with id ${id} not found`);
8586
+ const index$1 = data.findIndex((item) => item.id === id);
8587
+ if (index$1 === -1) throw new Error(`Item with id ${id} not found`);
8336
8588
  const updated = {
8337
- ...data[index],
8589
+ ...data[index$1],
8338
8590
  ...updateData
8339
8591
  };
8340
- data[index] = updated;
8592
+ data[index$1] = updated;
8341
8593
  return updated;
8342
8594
  },
8343
8595
  async delete(id) {
8344
- const index = data.findIndex((item) => item.id === id);
8345
- if (index === -1) throw new Error(`Item with id ${id} not found`);
8346
- data.splice(index, 1);
8596
+ const index$1 = data.findIndex((item) => item.id === id);
8597
+ if (index$1 === -1) throw new Error(`Item with id ${id} not found`);
8598
+ data.splice(index$1, 1);
8347
8599
  },
8348
8600
  async deleteMany(ids) {
8349
8601
  data = data.filter((item) => !ids.includes(item.id));
@@ -8352,4 +8604,4 @@ function createMemoryDataSource(initialData = []) {
8352
8604
  }
8353
8605
 
8354
8606
  //#endregion
8355
- export { AutoCrudTable, AutoForm, AutoTable, AutoTableActionBar, AutoTableSimpleFilters, CrudFormModal, DataTable, DataTableAdvancedToolbar, DataTableColumnHeader, DataTableFacetedFilter, DataTablePagination, DataTableToolbar, DataTableViewOptions, ExportDialog, ImportDialog, MultiCombobox, SchemaAdapter, cn, coerceRowValues, components, createActionsColumn, createEditFormSchema, createFormSchema, createMemoryDataSource, createSelectColumn, createTRPCDataSource, createTableSchema, dataSources, dataToCSV, deDE, downloadCSVTemplate, enUS, esES, exportAllToCSV, exportTableToCSV, formComponents, formatDate, frFR, generateCSVTemplate, getUrlParams, humanize, jaJP, koKR, noopToastAdapter, normalizeDataSourceConfig, normalizeOptions, parseAsArrayOf, parseAsInteger, parseAsString, parseAsStringEnum, parseCSV, parseImportFile, parseJSON, parseZodField, resolveLocale, setAutoCrudToolbarResolver, setSearchParams, useAutoCrudResource, useDataTable, useQueryState, useQueryStates, useReadableFilters, useUrlState, useUrlStates, zhCN };
8607
+ export { AutoCrudTable, AutoForm, AutoTable, AutoTableActionBar, AutoTableSimpleFilters, CrudFormModal, DataTable, DataTableAdvancedToolbar, DataTableColumnHeader, DataTableFacetedFilter, DataTablePagination, DataTableToolbar, DataTableViewOptions, ExportDialog, ImportDialog, MultiCombobox, SchemaAdapter, cn, coerceRowValues, components, createActionsColumn, createEditFormSchema, createFormSchema, createMemoryDataSource, createSelectColumn, createTRPCDataSource, createTableSchema, crudActions, dataSources, dataToCSV, deDE, downloadCSVTemplate, enUS, esES, exportAllToCSV, exportTableToCSV, formComponents, formatDate, frFR, generateCSVTemplate, getUrlParams, humanize, jaJP, koKR, noopToastAdapter, normalizeDataSourceConfig, normalizeOptions, parseAsArrayOf, parseAsInteger, parseAsString, parseAsStringEnum, parseCSV, parseImportFile, parseJSON, parseZodField, resolveLocale, setSearchParams, setToolbarResolver, useAutoCrudResource, useDataTable, useQueryState, useQueryStates, useReadableFilters, useUrlState, useUrlStates, zhCN };