@wordrhyme/auto-crud 1.2.5 → 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.cjs CHANGED
@@ -504,10 +504,10 @@ function getColumnVariant(column) {
504
504
  function splitKey(key) {
505
505
  const match = key.match(/^(.*?)(?:__(\d+))?$/);
506
506
  if (!match) return { base: key };
507
- const index = match[2] ? Number(match[2]) : void 0;
507
+ const index$1 = match[2] ? Number(match[2]) : void 0;
508
508
  return {
509
509
  base: match[1] ?? key,
510
- index
510
+ index: index$1
511
511
  };
512
512
  }
513
513
  function isFilterKey(key, columnIds) {
@@ -1299,7 +1299,7 @@ function SortableItem(props) {
1299
1299
  if (value === "") throw new Error(`\`${ITEM_NAME$1}\` value cannot be an empty string`);
1300
1300
  const context = useSortableContext(ITEM_NAME$1);
1301
1301
  const id = react.useId();
1302
- const { attributes, listeners, setNodeRef, setActivatorNodeRef, transform, transition, isDragging } = (0, __dnd_kit_sortable.useSortable)({
1302
+ const { attributes, listeners: listeners$1, setNodeRef, setActivatorNodeRef, transform, transition, isDragging } = (0, __dnd_kit_sortable.useSortable)({
1303
1303
  id: value,
1304
1304
  disabled
1305
1305
  });
@@ -1322,14 +1322,14 @@ function SortableItem(props) {
1322
1322
  const itemContext = react.useMemo(() => ({
1323
1323
  id,
1324
1324
  attributes,
1325
- listeners,
1325
+ listeners: listeners$1,
1326
1326
  setActivatorNodeRef,
1327
1327
  isDragging,
1328
1328
  disabled
1329
1329
  }), [
1330
1330
  id,
1331
1331
  attributes,
1332
- listeners,
1332
+ listeners$1,
1333
1333
  setActivatorNodeRef,
1334
1334
  isDragging,
1335
1335
  disabled
@@ -1344,7 +1344,7 @@ function SortableItem(props) {
1344
1344
  "data-slot": "sortable-item",
1345
1345
  ...itemProps,
1346
1346
  ...asHandle && !disabled ? attributes : {},
1347
- ...asHandle && !disabled ? listeners : {},
1347
+ ...asHandle && !disabled ? listeners$1 : {},
1348
1348
  ref: composedRef,
1349
1349
  style: composedStyle,
1350
1350
  className: cn("focus-visible:outline-hidden focus-visible:ring-1 focus-visible:ring-ring focus-visible:ring-offset-1", {
@@ -1571,9 +1571,9 @@ function DataTableFilterList({ table, debounceMs = DEBOUNCE_MS$2, throttleMs = T
1571
1571
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1572
1572
  role: "list",
1573
1573
  className: "flex max-h-[300px] flex-col gap-2 overflow-y-auto p-1",
1574
- children: filters.map((filter, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DataTableFilterItem$1, {
1574
+ children: filters.map((filter, index$1) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DataTableFilterItem$1, {
1575
1575
  filter,
1576
- index,
1576
+ index: index$1,
1577
1577
  filterItemId: `${id}-filter-${filter.filterId}`,
1578
1578
  joinOperator,
1579
1579
  setJoinOperator,
@@ -1614,7 +1614,7 @@ function DataTableFilterList({ table, debounceMs = DEBOUNCE_MS$2, throttleMs = T
1614
1614
  }) })]
1615
1615
  });
1616
1616
  }
1617
- function DataTableFilterItem$1({ filter, index, filterItemId, joinOperator, setJoinOperator, columns, onFilterUpdate, onFilterRemove }) {
1617
+ function DataTableFilterItem$1({ filter, index: index$1, filterItemId, joinOperator, setJoinOperator, columns, onFilterUpdate, onFilterRemove }) {
1618
1618
  const [showFieldSelector, setShowFieldSelector] = react.useState(false);
1619
1619
  const [showOperatorSelector, setShowOperatorSelector] = react.useState(false);
1620
1620
  const [showValueSelector, setShowValueSelector] = react.useState(false);
@@ -1652,10 +1652,10 @@ function DataTableFilterItem$1({ filter, index, filterItemId, joinOperator, setJ
1652
1652
  children: [
1653
1653
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1654
1654
  className: "min-w-[72px] text-center",
1655
- children: index === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1655
+ children: index$1 === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1656
1656
  className: "text-muted-foreground text-sm",
1657
1657
  children: "Where"
1658
- }) : index === 1 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(__pixpilot_shadcn.Select, {
1658
+ }) : index$1 === 1 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(__pixpilot_shadcn.Select, {
1659
1659
  value: joinOperator,
1660
1660
  onValueChange: (value) => setJoinOperator(value),
1661
1661
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(__pixpilot_shadcn.SelectTrigger, {
@@ -3088,10 +3088,10 @@ function AutoTableSimpleFilters({ table, shallow = false, filters: externalFilte
3088
3088
  "data-filter-leading": true,
3089
3089
  children: leading
3090
3090
  }) : null,
3091
- filterColumns.map((column, index) => {
3091
+ filterColumns.map((column, index$1) => {
3092
3092
  const meta = column.columnDef.meta;
3093
3093
  const value = getFilterValue(column.id);
3094
- const isHidden = !expanded && visibleCount !== null && index >= visibleCount;
3094
+ const isHidden = !expanded && visibleCount !== null && index$1 >= visibleCount;
3095
3095
  switch (meta.variant) {
3096
3096
  case "text": return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3097
3097
  className: isHidden ? "hidden" : "",
@@ -3503,7 +3503,7 @@ function focusFirst(candidates, preventScroll = false) {
3503
3503
  }
3504
3504
  }
3505
3505
  function wrapArray(array, startIndex) {
3506
- return array.map((_, index) => array[(startIndex + index) % array.length]);
3506
+ return array.map((_, index$1) => array[(startIndex + index$1) % array.length]);
3507
3507
  }
3508
3508
  function getDirectionAwareKey(key, dir) {
3509
3509
  if (dir !== "rtl") return key;
@@ -4046,9 +4046,9 @@ function parseZodField(schema) {
4046
4046
  if (def?.type === "enum") {
4047
4047
  let enumValues$1;
4048
4048
  if (def?.entries) {
4049
- const entries = def.entries;
4050
- if (Array.isArray(entries)) enumValues$1 = entries.map(String);
4051
- else if (typeof entries === "object") enumValues$1 = Object.keys(entries);
4049
+ const entries$1 = def.entries;
4050
+ if (Array.isArray(entries$1)) enumValues$1 = entries$1.map(String);
4051
+ else if (typeof entries$1 === "object") enumValues$1 = Object.keys(entries$1);
4052
4052
  } else if (def?.values) {
4053
4053
  const values = def.values;
4054
4054
  if (Array.isArray(values)) enumValues$1 = values.map(String);
@@ -4069,9 +4069,9 @@ function parseZodField(schema) {
4069
4069
  });
4070
4070
  let enumValues;
4071
4071
  if (def?.entries) {
4072
- const entries = def.entries;
4073
- if (Array.isArray(entries)) enumValues = entries.map(String);
4074
- else if (typeof entries === "object") enumValues = Object.keys(entries);
4072
+ const entries$1 = def.entries;
4073
+ if (Array.isArray(entries$1)) enumValues = entries$1.map(String);
4074
+ else if (typeof entries$1 === "object") enumValues = Object.keys(entries$1);
4075
4075
  } else if (def?.values) {
4076
4076
  const values = def.values;
4077
4077
  if (Array.isArray(values)) enumValues = values.map(String);
@@ -4266,6 +4266,10 @@ function createSelectColumn() {
4266
4266
  * 创建操作列
4267
4267
  */
4268
4268
  function createActionsColumn(items) {
4269
+ const renderActionComponent = (item, row) => {
4270
+ if (!item.component) return null;
4271
+ return typeof item.component === "function" ? item.component(item.getContext?.(row.original)) : item.component;
4272
+ };
4269
4273
  return {
4270
4274
  id: "actions",
4271
4275
  cell: ({ row }) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(__pixpilot_shadcn.DropdownMenu, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(__pixpilot_shadcn.DropdownMenuTrigger, {
@@ -4279,11 +4283,11 @@ function createActionsColumn(items) {
4279
4283
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__pixpilot_shadcn.DropdownMenuContent, {
4280
4284
  align: "end",
4281
4285
  className: "w-40",
4282
- children: items.map((item, i) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react.Fragment, { children: [item.separator && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__pixpilot_shadcn.DropdownMenuSeparator, {}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__pixpilot_shadcn.DropdownMenuItem, {
4286
+ children: items.map((item, i) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react.Fragment, { children: [item.separator && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__pixpilot_shadcn.DropdownMenuSeparator, {}), item.component ? renderActionComponent(item, row) : item.label && item.onClick ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__pixpilot_shadcn.DropdownMenuItem, {
4283
4287
  className: item.variant === "destructive" ? "text-destructive" : "",
4284
- onClick: () => item.onClick(row.original),
4288
+ onClick: () => item.onClick?.(row.original),
4285
4289
  children: item.label
4286
- })] }, i))
4290
+ }) : null] }, i))
4287
4291
  })] }),
4288
4292
  size: 40
4289
4293
  };
@@ -4584,34 +4588,36 @@ function AutoTableActionBar({ table, onDeleteSelected, onUpdateSelected, batchUp
4584
4588
  renderComponent,
4585
4589
  selectedRows
4586
4590
  ]);
4587
- const renderCustomAction = react.useCallback((action, index) => {
4588
- if (action.component) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react.Fragment, { children: renderComponent(action.component) }, `custom-${index}`);
4591
+ const renderCustomAction = react.useCallback((action, index$1) => {
4592
+ if (action.component) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react.Fragment, { children: renderComponent(action.component) }, `custom-${index$1}`);
4589
4593
  if (!action.label || !action.onClick) return null;
4590
4594
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ActionBarItem, {
4591
4595
  variant: action.variant === "destructive" ? "destructive" : "secondary",
4592
4596
  onClick: (event) => action.onClick?.(selectedRows, actionContext, event),
4593
4597
  children: action.label
4594
- }, `custom-${index}`);
4598
+ }, `custom-${index$1}`);
4595
4599
  }, [
4596
4600
  actionContext,
4597
4601
  renderComponent,
4598
4602
  selectedRows
4599
4603
  ]);
4600
4604
  const renderedActions = react.useMemo(() => {
4601
- const resolvedActions = typeof actions === "function" ? actions([
4605
+ const configuredActions = typeof actions === "function" ? actions([
4602
4606
  { type: "batchUpdate" },
4603
4607
  { type: "export" },
4604
4608
  { type: "delete" }
4605
4609
  ]) : actions;
4606
- if (!resolvedActions || resolvedActions.length === 0) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
4610
+ const resolvedActions = configuredActions?.filter((action) => !action.hidden);
4611
+ if (configuredActions === void 0) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
4607
4612
  renderBuiltinAction({ type: "batchUpdate" }),
4608
4613
  extraActions,
4609
4614
  showDefaultExport && renderBuiltinAction({ type: "export" }),
4610
4615
  renderBuiltinAction({ type: "delete" })
4611
4616
  ] });
4617
+ if (!resolvedActions || resolvedActions.length === 0) return null;
4612
4618
  if (!resolvedActions.some((action) => action.type !== "custom")) {
4613
- const startNodes = resolvedActions.filter(isBatchCustomAction).filter((action) => action.position === "start").map((action, index) => renderCustomAction(action, index));
4614
- const endNodes = resolvedActions.filter(isBatchCustomAction).filter((action) => action.position !== "start").map((action, index) => renderCustomAction(action, index));
4619
+ const startNodes = resolvedActions.filter(isBatchCustomAction).filter((action) => action.position === "start").map((action, index$1) => renderCustomAction(action, index$1));
4620
+ const endNodes = resolvedActions.filter(isBatchCustomAction).filter((action) => action.position !== "start").map((action, index$1) => renderCustomAction(action, index$1));
4615
4621
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
4616
4622
  startNodes,
4617
4623
  renderBuiltinAction({ type: "batchUpdate" }),
@@ -4621,8 +4627,8 @@ function AutoTableActionBar({ table, onDeleteSelected, onUpdateSelected, batchUp
4621
4627
  endNodes
4622
4628
  ] });
4623
4629
  }
4624
- return resolvedActions.map((action, index) => {
4625
- if (action.type === "custom") return renderCustomAction(action, index);
4630
+ return resolvedActions.map((action, index$1) => {
4631
+ if (action.type === "custom") return renderCustomAction(action, index$1);
4626
4632
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react.Fragment, { children: renderBuiltinAction(action) }, action.type);
4627
4633
  });
4628
4634
  }, [
@@ -5244,39 +5250,39 @@ function createEditFormSchema(schema, options) {
5244
5250
  //#endregion
5245
5251
  //#region src/lib/registries.ts
5246
5252
  function createRegistry(label) {
5247
- const entries = /* @__PURE__ */ new Map();
5248
- const listeners = /* @__PURE__ */ new Set();
5249
- let notifyScheduled = false;
5250
- const notify = () => {
5251
- if (notifyScheduled) return;
5252
- notifyScheduled = true;
5253
+ const entries$1 = /* @__PURE__ */ new Map();
5254
+ const listeners$1 = /* @__PURE__ */ new Set();
5255
+ let notifyScheduled$1 = false;
5256
+ const notify$1 = () => {
5257
+ if (notifyScheduled$1) return;
5258
+ notifyScheduled$1 = true;
5253
5259
  queueMicrotask(() => {
5254
- notifyScheduled = false;
5255
- for (const listener of listeners) listener();
5260
+ notifyScheduled$1 = false;
5261
+ for (const listener of listeners$1) listener();
5256
5262
  });
5257
5263
  };
5258
5264
  return {
5259
5265
  register(name, value) {
5260
- if (entries.has(name)) {
5266
+ if (entries$1.has(name)) {
5261
5267
  console.warn(`[AutoCrud] ${label} "${name}" is already registered.`);
5262
5268
  return;
5263
5269
  }
5264
- entries.set(name, value);
5265
- notify();
5270
+ entries$1.set(name, value);
5271
+ notify$1();
5266
5272
  },
5267
5273
  unregister(name) {
5268
- if (!entries.delete(name)) return;
5269
- notify();
5274
+ if (!entries$1.delete(name)) return;
5275
+ notify$1();
5270
5276
  },
5271
5277
  get(name) {
5272
- return entries.get(name);
5278
+ return entries$1.get(name);
5273
5279
  },
5274
5280
  all() {
5275
- return Object.fromEntries(entries);
5281
+ return Object.fromEntries(entries$1);
5276
5282
  },
5277
5283
  subscribe(listener) {
5278
- listeners.add(listener);
5279
- return () => listeners.delete(listener);
5284
+ listeners$1.add(listener);
5285
+ return () => listeners$1.delete(listener);
5280
5286
  }
5281
5287
  };
5282
5288
  }
@@ -5367,9 +5373,9 @@ const defaultFieldComponents = {
5367
5373
  }
5368
5374
  };
5369
5375
  function useRegistryVersion$1(subscribe) {
5370
- const [version, setVersion] = (0, react.useState)(0);
5376
+ const [version$1, setVersion] = (0, react.useState)(0);
5371
5377
  (0, react.useEffect)(() => subscribe(() => setVersion((current) => current + 1)), [subscribe]);
5372
- return version;
5378
+ return version$1;
5373
5379
  }
5374
5380
  function isDataSourceConfig(value) {
5375
5381
  return typeof value === "string" && value.length > 0;
@@ -5400,8 +5406,8 @@ function useFormDataSources(form, schema) {
5400
5406
  const loadVersions = /* @__PURE__ */ new Map();
5401
5407
  const controller = typeof AbortController === "undefined" ? void 0 : new AbortController();
5402
5408
  const loadFieldOptions = async (fieldName, source) => {
5403
- const version = (loadVersions.get(fieldName) ?? 0) + 1;
5404
- loadVersions.set(fieldName, version);
5409
+ const version$1 = (loadVersions.get(fieldName) ?? 0) + 1;
5410
+ loadVersions.set(fieldName, version$1);
5405
5411
  const entry = dataSources.get(source.key);
5406
5412
  if (!entry) {
5407
5413
  form.setFieldState(fieldName, (state) => {
@@ -5415,7 +5421,7 @@ function useFormDataSources(form, schema) {
5415
5421
  values: Object.fromEntries(entry.dependencies.map((dependency) => [dependency, form.getValuesIn(dependency)])),
5416
5422
  signal: controller?.signal
5417
5423
  }));
5418
- if (!active || loadVersions.get(fieldName) !== version) return;
5424
+ if (!active || loadVersions.get(fieldName) !== version$1) return;
5419
5425
  form.setFieldState(fieldName, (state) => {
5420
5426
  state.dataSource = options;
5421
5427
  });
@@ -6284,6 +6290,153 @@ function ImportDialog({ open, onOpenChange, onImport, columns = [], title, local
6284
6290
  });
6285
6291
  }
6286
6292
 
6293
+ //#endregion
6294
+ //#region src/lib/crud-actions.ts
6295
+ const entries = /* @__PURE__ */ new Map();
6296
+ const index = /* @__PURE__ */ new Map();
6297
+ const listeners = /* @__PURE__ */ new Set();
6298
+ let seq = 0;
6299
+ let version = 0;
6300
+ let notifyScheduled = false;
6301
+ function actionKey(registration, action, index$1) {
6302
+ return [
6303
+ registration.ownerId,
6304
+ registration.targetId,
6305
+ registration.zone,
6306
+ action.id ?? action.type,
6307
+ index$1
6308
+ ].join(":");
6309
+ }
6310
+ function indexKey(targetId, zone) {
6311
+ return `${targetId}:${zone}`;
6312
+ }
6313
+ function addToIndex(key, entry) {
6314
+ const bucketKey = indexKey(entry.targetId, entry.zone);
6315
+ const bucket = index.get(bucketKey) ?? /* @__PURE__ */ new Set();
6316
+ bucket.add(key);
6317
+ index.set(bucketKey, bucket);
6318
+ }
6319
+ function removeFromIndex(key, entry) {
6320
+ const bucketKey = indexKey(entry.targetId, entry.zone);
6321
+ const bucket = index.get(bucketKey);
6322
+ if (!bucket) return;
6323
+ bucket.delete(key);
6324
+ if (bucket.size === 0) index.delete(bucketKey);
6325
+ }
6326
+ function notify() {
6327
+ version += 1;
6328
+ if (notifyScheduled) return;
6329
+ notifyScheduled = true;
6330
+ queueMicrotask(() => {
6331
+ notifyScheduled = false;
6332
+ for (const listener of listeners) listener();
6333
+ });
6334
+ }
6335
+ function unregisterOwnerActions(ownerId, options = {}, shouldNotify = true) {
6336
+ let changed = false;
6337
+ for (const [key, entry] of entries) {
6338
+ if (entry.ownerId !== ownerId) continue;
6339
+ if (options.targetId && entry.targetId !== options.targetId) continue;
6340
+ if (options.zone && entry.zone !== options.zone) continue;
6341
+ entries.delete(key);
6342
+ removeFromIndex(key, entry);
6343
+ changed = true;
6344
+ }
6345
+ if (changed && shouldNotify) notify();
6346
+ }
6347
+ function sortEntries(left, right) {
6348
+ const orderDiff = left.order - right.order;
6349
+ if (orderDiff !== 0) return orderDiff;
6350
+ const ownerDiff = left.ownerId.localeCompare(right.ownerId);
6351
+ if (ownerDiff !== 0) return ownerDiff;
6352
+ return left.seq - right.seq;
6353
+ }
6354
+ function withoutRegistryMeta(action) {
6355
+ const { id, order,...rest } = action;
6356
+ return rest;
6357
+ }
6358
+ function isCustomAction$1(action) {
6359
+ return action.type === "custom";
6360
+ }
6361
+ function resolveActions$1(targetId, zone, ownerActions) {
6362
+ const baseActions = ownerActions.filter((action) => !action.hidden).map((action) => withoutRegistryMeta(action));
6363
+ if (!targetId) return baseActions;
6364
+ const registered = crudActions.get(targetId, zone);
6365
+ if (registered.length === 0) return baseActions;
6366
+ const nextActions = [...baseActions];
6367
+ const startCustomActions = [];
6368
+ const endCustomActions = [];
6369
+ for (const entry of registered) {
6370
+ const action = entry.action;
6371
+ if (action.hidden && isCustomAction$1(action)) continue;
6372
+ if (isCustomAction$1(action)) {
6373
+ const custom = withoutRegistryMeta(action);
6374
+ if (custom.position === "start") startCustomActions.push(custom);
6375
+ else endCustomActions.push(custom);
6376
+ continue;
6377
+ }
6378
+ const existingIndex = nextActions.findIndex((item) => item.type === action.type);
6379
+ if (action.hidden) {
6380
+ if (existingIndex >= 0) nextActions.splice(existingIndex, 1);
6381
+ continue;
6382
+ }
6383
+ const builtin = withoutRegistryMeta(action);
6384
+ if (existingIndex >= 0) nextActions[existingIndex] = {
6385
+ ...nextActions[existingIndex],
6386
+ ...builtin
6387
+ };
6388
+ else nextActions.push(builtin);
6389
+ }
6390
+ return [
6391
+ ...startCustomActions,
6392
+ ...nextActions,
6393
+ ...endCustomActions
6394
+ ];
6395
+ }
6396
+ const crudActions = {
6397
+ register(registration) {
6398
+ unregisterOwnerActions(registration.ownerId, {
6399
+ targetId: registration.targetId,
6400
+ zone: registration.zone
6401
+ }, false);
6402
+ registration.actions.forEach((action, index$1) => {
6403
+ const key = actionKey(registration, action, index$1);
6404
+ const entry = {
6405
+ targetId: registration.targetId,
6406
+ zone: registration.zone,
6407
+ ownerId: registration.ownerId,
6408
+ action,
6409
+ order: action.order ?? 100,
6410
+ seq: seq++
6411
+ };
6412
+ entries.set(key, entry);
6413
+ addToIndex(key, entry);
6414
+ });
6415
+ notify();
6416
+ },
6417
+ unregister: unregisterOwnerActions,
6418
+ clear() {
6419
+ if (entries.size === 0) return;
6420
+ entries.clear();
6421
+ index.clear();
6422
+ notify();
6423
+ },
6424
+ get(targetId, zone) {
6425
+ return Array.from(index.get(indexKey(targetId, zone)) ?? []).flatMap((key) => {
6426
+ const entry = entries.get(key);
6427
+ return entry ? [entry] : [];
6428
+ }).sort(sortEntries);
6429
+ },
6430
+ resolve: resolveActions$1,
6431
+ subscribe(listener) {
6432
+ listeners.add(listener);
6433
+ return () => listeners.delete(listener);
6434
+ },
6435
+ getSnapshot() {
6436
+ return version;
6437
+ }
6438
+ };
6439
+
6287
6440
  //#endregion
6288
6441
  //#region src/components/auto-crud/auto-crud-table.tsx
6289
6442
  let autoCrudToolbarResolver = null;
@@ -6297,25 +6450,64 @@ function getDefaultToolbarActions() {
6297
6450
  { type: "create" }
6298
6451
  ];
6299
6452
  }
6453
+ function getDefaultRowActions() {
6454
+ return [
6455
+ { type: "view" },
6456
+ { type: "edit" },
6457
+ { type: "copy" },
6458
+ { type: "delete" }
6459
+ ];
6460
+ }
6461
+ function getDefaultBatchActions() {
6462
+ return [{ type: "batchUpdate" }, { type: "delete" }];
6463
+ }
6464
+ function isCustomAction(action) {
6465
+ return action.type === "custom";
6466
+ }
6467
+ function resolveOwnerActions(config, defaults) {
6468
+ const resolved = typeof config === "function" ? config([...defaults]) : config;
6469
+ const items = resolved !== void 0 && resolved.length > 0 ? [...resolved] : [];
6470
+ if (items.length === 0) return [...defaults];
6471
+ if (items.some((item) => !isCustomAction(item))) return items;
6472
+ const startItems = items.filter((item) => isCustomAction(item) && item.position === "start");
6473
+ const endItems = items.filter((item) => isCustomAction(item) && item.position !== "start");
6474
+ return [
6475
+ ...startItems,
6476
+ ...defaults,
6477
+ ...endItems
6478
+ ];
6479
+ }
6300
6480
  function resolveOwnerToolbarActions(toolbar) {
6301
- const resolved = typeof toolbar === "function" ? toolbar(getDefaultToolbarActions()) : toolbar;
6302
- return resolved !== void 0 && resolved.length > 0 ? [...resolved] : getDefaultToolbarActions();
6481
+ return resolveOwnerActions(toolbar, getDefaultToolbarActions());
6482
+ }
6483
+ function resolveOwnerRowActions(actions) {
6484
+ return resolveOwnerActions(actions, getDefaultRowActions());
6485
+ }
6486
+ function resolveOwnerBatchActions(actions) {
6487
+ return resolveOwnerActions(actions, getDefaultBatchActions());
6488
+ }
6489
+ function isUnifiedActionsConfig(actions) {
6490
+ return typeof actions === "object" && actions !== null && !Array.isArray(actions);
6491
+ }
6492
+ function readRowId(row, idKey) {
6493
+ const value = row[idKey];
6494
+ return value === null || value === void 0 ? void 0 : String(value);
6303
6495
  }
6304
6496
  function readRowIds(rows, idKey) {
6305
6497
  return rows.flatMap((row) => {
6306
- const value = row[idKey];
6307
- return value === null || value === void 0 ? [] : [String(value)];
6498
+ const value = readRowId(row, idKey);
6499
+ return value === void 0 ? [] : [value];
6308
6500
  });
6309
6501
  }
6310
6502
  function areStringArraysEqual(left, right) {
6311
- return left.length === right.length && left.every((value, index) => value === right[index]);
6503
+ return left.length === right.length && left.every((value, index$1) => value === right[index$1]);
6312
6504
  }
6313
6505
  function haveSameRowSelection(left, right, idKey) {
6314
6506
  if (left.length !== right.length) return false;
6315
6507
  const leftIds = readRowIds(left, idKey);
6316
6508
  const rightIds = readRowIds(right, idKey);
6317
6509
  if (leftIds.length === left.length && rightIds.length === right.length) return areStringArraysEqual(leftIds, rightIds);
6318
- return left.every((row, index) => row === right[index]);
6510
+ return left.every((row, index$1) => row === right[index$1]);
6319
6511
  }
6320
6512
  function readCreateAction(actions) {
6321
6513
  return actions.find((action) => action.type === "create");
@@ -6384,9 +6576,12 @@ function shouldLoadDynamicFilterOptions(config) {
6384
6576
  return normalizeDataSourceConfig(getFilterDataSource(config)) !== void 0;
6385
6577
  }
6386
6578
  function useRegistryVersion(subscribe) {
6387
- const [version, setVersion] = react.useState(0);
6579
+ const [version$1, setVersion] = react.useState(0);
6388
6580
  react.useEffect(() => subscribe(() => setVersion((current) => current + 1)), [subscribe]);
6389
- return version;
6581
+ return version$1;
6582
+ }
6583
+ function useCrudActionsVersion() {
6584
+ return react.useSyncExternalStore(crudActions.subscribe, crudActions.getSnapshot, crudActions.getSnapshot);
6390
6585
  }
6391
6586
  function useDynamicFilterOptions(fields) {
6392
6587
  const registryVersion = useRegistryVersion(dataSources.subscribe);
@@ -6422,9 +6617,9 @@ function useDynamicFilterOptions(fields) {
6422
6617
  if (!controller?.signal.aborted) console.warn(`[AutoCrud] Failed to load filter data source "${source.key}".`, error);
6423
6618
  return [field, []];
6424
6619
  }
6425
- })).then((entries) => {
6620
+ })).then((entries$1) => {
6426
6621
  if (!active) return;
6427
- setOptionsByField(Object.fromEntries(entries));
6622
+ setOptionsByField(Object.fromEntries(entries$1));
6428
6623
  });
6429
6624
  return () => {
6430
6625
  active = false;
@@ -6691,13 +6886,23 @@ function ViewModal({ open, onOpenChange, variant, data, schema, fields: fieldCon
6691
6886
  /**
6692
6887
  * 解析列表行操作
6693
6888
  */
6694
- function resolveActions(actionsOrFn, defaults, rowActionsLocale) {
6695
- const items = typeof actionsOrFn === "function" ? actionsOrFn([
6889
+ function resolveActions(actionsOrFn, defaults, rowActionsLocale, context) {
6890
+ const resolvedItems = typeof actionsOrFn === "function" ? actionsOrFn([
6696
6891
  { type: "view" },
6697
6892
  { type: "edit" },
6698
6893
  { type: "copy" },
6699
6894
  { type: "delete" }
6700
6895
  ]) : actionsOrFn;
6896
+ const getContext = (row) => ({
6897
+ crudId: context.crudId,
6898
+ idKey: context.idKey,
6899
+ row,
6900
+ rowId: readRowId(row, context.idKey),
6901
+ openView: defaults.openView,
6902
+ ...defaults.openEdit ? { openEdit: defaults.openEdit } : {},
6903
+ ...defaults.copyRow ? { copyRow: defaults.copyRow } : {},
6904
+ ...defaults.openDelete ? { openDelete: defaults.openDelete } : {}
6905
+ });
6701
6906
  const defaultItems = [
6702
6907
  {
6703
6908
  label: rowActionsLocale.view,
@@ -6718,17 +6923,23 @@ function resolveActions(actionsOrFn, defaults, rowActionsLocale) {
6718
6923
  variant: "destructive"
6719
6924
  }] : []
6720
6925
  ];
6721
- if (!items || items.length === 0) return defaultItems;
6926
+ if (resolvedItems === void 0) return defaultItems;
6927
+ const items = resolvedItems.filter((item) => !item.hidden);
6928
+ if (items.length === 0) return [];
6722
6929
  if (!items.some((i) => i.type !== "custom")) {
6723
- const startItems = items.filter((i) => i.type === "custom" && i.position === "start").map(({ label, onClick, separator, variant }) => ({
6930
+ const startItems = items.filter((i) => i.type === "custom" && i.position === "start").map(({ label, onClick, component, separator, variant }) => ({
6724
6931
  label,
6725
6932
  onClick,
6933
+ component,
6934
+ getContext,
6726
6935
  separator,
6727
6936
  variant
6728
6937
  }));
6729
- const endItems = items.filter((i) => i.type === "custom" && i.position !== "start").map(({ label, onClick, separator, variant }) => ({
6938
+ const endItems = items.filter((i) => i.type === "custom" && i.position !== "start").map(({ label, onClick, component, separator, variant }) => ({
6730
6939
  label,
6731
6940
  onClick,
6941
+ component,
6942
+ getContext,
6732
6943
  separator,
6733
6944
  variant
6734
6945
  }));
@@ -6755,6 +6966,8 @@ function resolveActions(actionsOrFn, defaults, rowActionsLocale) {
6755
6966
  if (item.type === "custom") return [{
6756
6967
  label: item.label,
6757
6968
  onClick: item.onClick,
6969
+ component: item.component,
6970
+ getContext,
6758
6971
  separator: item.separator,
6759
6972
  variant: item.variant
6760
6973
  }];
@@ -6794,7 +7007,32 @@ function AutoCrudTable({ id, title, description, schema, resource, fields, table
6794
7007
  const getSelectedRowsRef = react.useRef(null);
6795
7008
  const dynamicFilterOptions = useDynamicFilterOptions(resolvedFields);
6796
7009
  const resourceIdKey = resource.idKey ?? "id";
6797
- const ownerToolbarActions = react.useMemo(() => resolveOwnerToolbarActions(toolbar ?? toolbarActions), [toolbar, toolbarActions]);
7010
+ const actionRegistryVersion = useCrudActionsVersion();
7011
+ const unifiedActions = react.useMemo(() => {
7012
+ if (isUnifiedActionsConfig(actionItems)) return actionItems;
7013
+ return { row: actionItems };
7014
+ }, [actionItems]);
7015
+ const ownerToolbarConfig = unifiedActions.toolbar ?? toolbar ?? toolbarActions;
7016
+ const ownerRowConfig = unifiedActions.row;
7017
+ const ownerBatchConfig = unifiedActions.batch ?? tableConfig?.batchActions;
7018
+ const ownerToolbarActions = react.useMemo(() => resolveOwnerToolbarActions(ownerToolbarConfig), [ownerToolbarConfig]);
7019
+ const ownerRowActions = react.useMemo(() => resolveOwnerRowActions(ownerRowConfig), [ownerRowConfig]);
7020
+ const ownerBatchActions = react.useMemo(() => resolveOwnerBatchActions(ownerBatchConfig), [ownerBatchConfig]);
7021
+ const registryToolbarActions = react.useMemo(() => crudActions.resolve(id, "toolbar", ownerToolbarActions), [
7022
+ id,
7023
+ ownerToolbarActions,
7024
+ actionRegistryVersion
7025
+ ]);
7026
+ const registryRowActions = react.useMemo(() => crudActions.resolve(id, "row", ownerRowActions), [
7027
+ id,
7028
+ ownerRowActions,
7029
+ actionRegistryVersion
7030
+ ]);
7031
+ const registryBatchActions = react.useMemo(() => crudActions.resolve(id, "batch", ownerBatchActions), [
7032
+ id,
7033
+ ownerBatchActions,
7034
+ actionRegistryVersion
7035
+ ]);
6798
7036
  const selectedCount = selectedRows.length;
6799
7037
  const selectedRowIds = react.useMemo(() => readRowIds(selectedRows, resourceIdKey), [selectedRows, resourceIdKey]);
6800
7038
  const rowIds = react.useMemo(() => readRowIds(resource.tableData.data, resourceIdKey), [resource.tableData.data, resourceIdKey]);
@@ -6803,7 +7041,7 @@ function AutoCrudTable({ id, title, description, schema, resource, fields, table
6803
7041
  }, [resourceIdKey]);
6804
7042
  const ownerCreateAction = react.useMemo(() => readCreateAction(ownerToolbarActions), [ownerToolbarActions]);
6805
7043
  const toolbarOpenCreate = can.create ? ownerCreateAction?.onClick ?? onCreate ?? resource.handlers.openCreate : void 0;
6806
- const resolvedToolbarActions = resolveToolbarActionsWithResolver(id, ownerToolbarActions, react.useMemo(() => ({
7044
+ const toolbarContext = react.useMemo(() => ({
6807
7045
  crudId: id ?? "",
6808
7046
  idKey: resourceIdKey,
6809
7047
  rowIds,
@@ -6817,7 +7055,8 @@ function AutoCrudTable({ id, title, description, schema, resource, fields, table
6817
7055
  selectedRowIds,
6818
7056
  selectedCount,
6819
7057
  toolbarOpenCreate
6820
- ]));
7058
+ ]);
7059
+ const resolvedToolbarActions = resolveToolbarActionsWithResolver(id, registryToolbarActions, toolbarContext);
6821
7060
  const importColumns = react.useMemo(() => {
6822
7061
  const shape = resolvedSchema.shape;
6823
7062
  const denySet = new Set(denyFields ?? []);
@@ -6887,6 +7126,30 @@ function AutoCrudTable({ id, title, description, schema, resource, fields, table
6887
7126
  resource.modal.copySource,
6888
7127
  resource.modal.selected
6889
7128
  ]);
7129
+ const rowActionDefaults = react.useMemo(() => ({
7130
+ openView: resource.handlers.openView,
7131
+ openEdit: can.update ? resource.handlers.openEdit : void 0,
7132
+ copyRow: can.create ? resource.handlers.copyRow : void 0,
7133
+ openDelete: can.delete ? resource.handlers.openDelete : void 0
7134
+ }), [
7135
+ can.create,
7136
+ can.delete,
7137
+ can.update,
7138
+ resource.handlers.copyRow,
7139
+ resource.handlers.openDelete,
7140
+ resource.handlers.openEdit,
7141
+ resource.handlers.openView
7142
+ ]);
7143
+ const tableRowActions = react.useMemo(() => resolveActions(registryRowActions, rowActionDefaults, locale.rowActions, {
7144
+ crudId: id ?? "",
7145
+ idKey: resourceIdKey
7146
+ }), [
7147
+ id,
7148
+ locale.rowActions,
7149
+ registryRowActions,
7150
+ resourceIdKey,
7151
+ rowActionDefaults
7152
+ ]);
6890
7153
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6891
7154
  className: "space-y-4",
6892
7155
  children: [
@@ -6923,17 +7186,11 @@ function AutoCrudTable({ id, title, description, schema, resource, fields, table
6923
7186
  }, "create");
6924
7187
  return null;
6925
7188
  };
6926
- if (!resolvedToolbarActions || resolvedToolbarActions.length === 0) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6927
- className: "flex items-center gap-2",
6928
- children: [
6929
- renderBuiltinButton("import"),
6930
- renderBuiltinButton("export"),
6931
- renderBuiltinButton("create")
6932
- ]
6933
- });
7189
+ const renderToolbarComponent = (component) => typeof component === "function" ? component(toolbarContext) : component;
7190
+ if (!resolvedToolbarActions || resolvedToolbarActions.length === 0) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: "flex items-center gap-2" });
6934
7191
  if (!resolvedToolbarActions.some((i) => i.type !== "custom")) {
6935
- const startNodes = resolvedToolbarActions.filter((i) => i.type === "custom" && i.position === "start").map((a, i) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react.Fragment, { children: a.component }, `start-${i}`));
6936
- const endNodes = resolvedToolbarActions.filter((i) => i.type === "custom" && i.position !== "start").map((a, i) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react.Fragment, { children: a.component }, `end-${i}`));
7192
+ const startNodes = resolvedToolbarActions.filter((i) => i.type === "custom" && i.position === "start").map((a, i) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react.Fragment, { children: renderToolbarComponent(a.component) }, `start-${i}`));
7193
+ const endNodes = resolvedToolbarActions.filter((i) => i.type === "custom" && i.position !== "start").map((a, i) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react.Fragment, { children: renderToolbarComponent(a.component) }, `end-${i}`));
6937
7194
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6938
7195
  className: "flex items-center gap-2",
6939
7196
  children: [
@@ -6947,10 +7204,10 @@ function AutoCrudTable({ id, title, description, schema, resource, fields, table
6947
7204
  }
6948
7205
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
6949
7206
  className: "flex items-center gap-2",
6950
- children: resolvedToolbarActions.map((action, index) => {
6951
- if (action.type === "custom") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react.Fragment, { children: action.component }, `custom-${index}`);
7207
+ children: resolvedToolbarActions.map((action, index$1) => {
7208
+ if (action.type === "custom") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react.Fragment, { children: renderToolbarComponent(action.component) }, `custom-${index$1}`);
6952
7209
  if (!(action.type === "import" ? canImport : action.type === "export" ? canExport : can.create)) return null;
6953
- if (action.component) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react.Fragment, { children: action.component }, action.type);
7210
+ if (action.component) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react.Fragment, { children: renderToolbarComponent(action.component) }, action.type);
6954
7211
  return renderBuiltinButton(action.type, {
6955
7212
  onClick: action.onClick,
6956
7213
  label: action.label
@@ -6967,16 +7224,11 @@ function AutoCrudTable({ id, title, description, schema, resource, fields, table
6967
7224
  exclude: hiddenColumns,
6968
7225
  filterMode: tableConfig?.filterModes,
6969
7226
  search: searchConfig,
6970
- actions: resolveActions(actionItems, {
6971
- openView: resource.handlers.openView,
6972
- openEdit: can.update ? resource.handlers.openEdit : void 0,
6973
- copyRow: can.create ? resource.handlers.copyRow : void 0,
6974
- openDelete: can.delete ? resource.handlers.openDelete : void 0
6975
- }, locale.rowActions),
7227
+ actions: tableRowActions,
6976
7228
  onDeleteSelected: can.delete ? resource.handlers.deleteMany : void 0,
6977
7229
  onUpdateSelected: can.update ? resource.handlers.updateMany : void 0,
6978
7230
  batchUpdateFields: can.update ? batchFields : void 0,
6979
- actionBarActions: tableConfig?.batchActions,
7231
+ actionBarActions: registryBatchActions,
6980
7232
  enableExport: canExport,
6981
7233
  showDefaultExport: false,
6982
7234
  initialSorting: tableConfig?.defaultSort,
@@ -8374,19 +8626,19 @@ function createMemoryDataSource(initialData = []) {
8374
8626
  return newItem;
8375
8627
  },
8376
8628
  async update(id, updateData) {
8377
- const index = data.findIndex((item) => item.id === id);
8378
- if (index === -1) throw new Error(`Item with id ${id} not found`);
8629
+ const index$1 = data.findIndex((item) => item.id === id);
8630
+ if (index$1 === -1) throw new Error(`Item with id ${id} not found`);
8379
8631
  const updated = {
8380
- ...data[index],
8632
+ ...data[index$1],
8381
8633
  ...updateData
8382
8634
  };
8383
- data[index] = updated;
8635
+ data[index$1] = updated;
8384
8636
  return updated;
8385
8637
  },
8386
8638
  async delete(id) {
8387
- const index = data.findIndex((item) => item.id === id);
8388
- if (index === -1) throw new Error(`Item with id ${id} not found`);
8389
- data.splice(index, 1);
8639
+ const index$1 = data.findIndex((item) => item.id === id);
8640
+ if (index$1 === -1) throw new Error(`Item with id ${id} not found`);
8641
+ data.splice(index$1, 1);
8390
8642
  },
8391
8643
  async deleteMany(ids) {
8392
8644
  data = data.filter((item) => !ids.includes(item.id));
@@ -8422,6 +8674,7 @@ exports.createMemoryDataSource = createMemoryDataSource;
8422
8674
  exports.createSelectColumn = createSelectColumn;
8423
8675
  exports.createTRPCDataSource = createTRPCDataSource;
8424
8676
  exports.createTableSchema = createTableSchema;
8677
+ exports.crudActions = crudActions;
8425
8678
  exports.dataSources = dataSources;
8426
8679
  exports.dataToCSV = dataToCSV;
8427
8680
  exports.deDE = deDE;