@dataverse-kit/export-engine 1.4.0 → 1.5.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.
Files changed (3) hide show
  1. package/dist/index.cjs +321 -39
  2. package/dist/index.mjs +321 -39
  3. package/package.json +1 -1
package/dist/index.cjs CHANGED
@@ -12810,6 +12810,7 @@ function buildV8Body(args) {
12810
12810
  handlerBlock = "",
12811
12811
  rowCommandsLiteral = "",
12812
12812
  deleteDialogJsx = "",
12813
+ createDialogJsx = "",
12813
12814
  commandBarBlock = "",
12814
12815
  selectionModeAttr = "",
12815
12816
  onSelectionChangedAttr = ""
@@ -12836,7 +12837,7 @@ const ${componentName}: React.FC<{ items: Record<string, unknown>[] }> = ({ item
12836
12837
  editable
12837
12838
  editTrigger="click"
12838
12839
  getKey={(it) => String((it as { __rowIndex?: number }).__rowIndex ?? '')}${selectionModeAttr}${onSelectionChangedAttr}${onValueChangeAttr}${rowCommandsAttr}
12839
- />${editableAggregate}${editablePagination}${saveStatusJsx}${deleteDialogJsx}
12840
+ />${editableAggregate}${editablePagination}${saveStatusJsx}${deleteDialogJsx}${createDialogJsx}
12840
12841
  </div>
12841
12842
  );
12842
12843
  };`;
@@ -12889,6 +12890,7 @@ function buildLiveReadOnlyV8Body(args) {
12889
12890
  handlerBlock = "",
12890
12891
  rowCommandsLiteral = "",
12891
12892
  deleteDialogJsx = "",
12893
+ createDialogJsx = "",
12892
12894
  commandBarBlock = ""
12893
12895
  } = args;
12894
12896
  const rowCommandsAttr = rowCommandsLiteral ? `
@@ -12917,7 +12919,7 @@ const ${helperName}: React.FC = () => {
12917
12919
  ] as ColumnDef[]}
12918
12920
  registry={registry}
12919
12921
  getKey={(it) => String((it as { __rowIndex?: number }).__rowIndex ?? '')}${rowCommandsAttr}
12920
- />${flatAggregateInner}${flatPaginationInner}${deleteDialogJsx}
12922
+ />${flatAggregateInner}${flatPaginationInner}${deleteDialogJsx}${createDialogJsx}
12921
12923
  </div>
12922
12924
  );
12923
12925
  };`;
@@ -12963,6 +12965,7 @@ function buildCardV8LiveBody(args) {
12963
12965
  handlerBlock = "",
12964
12966
  rowCommandsLiteral = "",
12965
12967
  deleteDialogJsx = "",
12968
+ createDialogJsx = "",
12966
12969
  commandBarBlock = "",
12967
12970
  selectionModeAttr = "",
12968
12971
  onSelectionChangedAttr = ""
@@ -12994,7 +12997,7 @@ const ${helperName}: React.FC = () => {
12994
12997
  registry={registry}
12995
12998
  getKey={(it) => String((it as { __rowIndex?: number }).__rowIndex ?? '')}${selectionModeAttr}${onSelectionChangedAttr}
12996
12999
  card={{ ${cardConfigLiteral} }}${rowCommandsAttr}
12997
- />${flatAggregateInner}${flatPaginationInner}${deleteDialogJsx}
13000
+ />${flatAggregateInner}${flatPaginationInner}${deleteDialogJsx}${createDialogJsx}
12998
13001
  </div>
12999
13002
  );
13000
13003
  };`;
@@ -13320,6 +13323,7 @@ function buildNestedSubgridBody(args) {
13320
13323
  commandBarBlock,
13321
13324
  handlerBlock,
13322
13325
  deleteDialogJsx,
13326
+ createDialogJsx,
13323
13327
  hasSelection,
13324
13328
  hasHandler,
13325
13329
  dataverseHooks,
@@ -13375,7 +13379,7 @@ const ${componentName}: React.FC<{ items: Record<string, unknown>[] }> = ({ item
13375
13379
  calloutMaxRows: ${calloutMaxRows},
13376
13380
  hoverDelay: ${hoverDelay},${childEditableLiteral}
13377
13381
  }}
13378
- />${deleteDialogJsx}${childSaveStatusJsx}
13382
+ />${deleteDialogJsx}${createDialogJsx}${childSaveStatusJsx}
13379
13383
  </div>
13380
13384
  );
13381
13385
  };`;
@@ -13418,6 +13422,7 @@ function buildFocusedViewSubgridBody(args) {
13418
13422
  commandBarBlock,
13419
13423
  handlerBlock,
13420
13424
  deleteDialogJsx,
13425
+ createDialogJsx,
13421
13426
  hasSelection,
13422
13427
  hasHandler,
13423
13428
  dataverseHooks,
@@ -13471,7 +13476,7 @@ const ${componentName}: React.FC<{ items: Record<string, unknown>[] }> = ({ item
13471
13476
  childSelectionMode: ${JSON.stringify(childSelectionMode)},
13472
13477
  childSelectionGating: ${JSON.stringify(childSelectionGating)},${childEditableLiteral}
13473
13478
  }}
13474
- />${deleteDialogJsx}${childSaveStatusJsx}
13479
+ />${deleteDialogJsx}${createDialogJsx}${childSaveStatusJsx}
13475
13480
  </div>
13476
13481
  );
13477
13482
  };`;
@@ -14250,14 +14255,182 @@ function effectiveMinSelection(item) {
14250
14255
  if (typeof item.minSelectionCount === "number") return item.minSelectionCount;
14251
14256
  return DEFAULT_MIN_SEL_BY_ACTION[item.actionType ?? "custom"] ?? 0;
14252
14257
  }
14258
+ function buildExportColumnsLiteral(cols) {
14259
+ return cols.map(
14260
+ (c) => `{ key: ${JSON.stringify(c.fieldName)}, name: ${JSON.stringify(c.displayName ?? c.fieldName)} }`
14261
+ ).join(", ");
14262
+ }
14263
+ var CREATE_FIELD_DENYLIST = /* @__PURE__ */ new Set([
14264
+ "createdon",
14265
+ "createdby",
14266
+ "createdonbehalfby",
14267
+ "modifiedon",
14268
+ "modifiedby",
14269
+ "modifiedonbehalfby",
14270
+ "owningbusinessunit",
14271
+ "owningteam",
14272
+ "owninguser",
14273
+ "ownerid",
14274
+ "versionnumber",
14275
+ "overriddencreatedon",
14276
+ "importsequencenumber",
14277
+ "timezoneruleversionnumber",
14278
+ "utcconversiontimezonecode",
14279
+ "statecode",
14280
+ "statuscode"
14281
+ ]);
14282
+ function buildCreateFields(cols, primaryIdAttribute) {
14283
+ const primaryLower = (primaryIdAttribute ?? "").toLowerCase();
14284
+ const entries = [];
14285
+ for (const c of cols) {
14286
+ if (!c.fieldName) continue;
14287
+ const logical = (c.attributeLogicalName ?? c.fieldName).toLowerCase();
14288
+ if (logical === primaryLower) continue;
14289
+ if (c.isLocked) continue;
14290
+ if (c.dataType === "lookup" || c.dataType === "image") continue;
14291
+ if (CREATE_FIELD_DENYLIST.has(logical)) continue;
14292
+ const label = c.displayName ?? c.fieldName;
14293
+ let kind = "text";
14294
+ let inputType;
14295
+ let optionsLit = "";
14296
+ switch (c.dataType) {
14297
+ case "numeric":
14298
+ case "currency":
14299
+ kind = "number";
14300
+ inputType = "number";
14301
+ break;
14302
+ case "boolean":
14303
+ kind = "boolean";
14304
+ break;
14305
+ case "date":
14306
+ kind = "date";
14307
+ inputType = "date";
14308
+ break;
14309
+ case "datetime":
14310
+ kind = "date";
14311
+ inputType = "datetime-local";
14312
+ break;
14313
+ case "optionset": {
14314
+ const opts = c.rendererConfig?.options ?? [];
14315
+ if (opts.length === 0) {
14316
+ kind = "number";
14317
+ inputType = "number";
14318
+ } else {
14319
+ kind = "optionset";
14320
+ optionsLit = opts.map(
14321
+ (o) => `{ key: ${JSON.stringify(o.value)}, text: ${JSON.stringify(o.label)} }`
14322
+ ).join(", ");
14323
+ }
14324
+ break;
14325
+ }
14326
+ case "email":
14327
+ kind = "text";
14328
+ inputType = "email";
14329
+ break;
14330
+ case "phone":
14331
+ kind = "text";
14332
+ inputType = "tel";
14333
+ break;
14334
+ case "url":
14335
+ kind = "text";
14336
+ inputType = "url";
14337
+ break;
14338
+ default:
14339
+ kind = "text";
14340
+ break;
14341
+ }
14342
+ const payloadKey = c.attributeLogicalName ?? c.fieldName;
14343
+ const parts = [
14344
+ `key: ${JSON.stringify(payloadKey)}`,
14345
+ `label: ${JSON.stringify(label)}`,
14346
+ `kind: ${JSON.stringify(kind)}`
14347
+ ];
14348
+ if (inputType) parts.push(`inputType: ${JSON.stringify(inputType)}`);
14349
+ if (kind === "optionset") parts.push(`options: [${optionsLit}]`);
14350
+ entries.push(`{ ${parts.join(", ")} }`);
14351
+ }
14352
+ return entries.join(", ");
14353
+ }
14253
14354
  function buildGridCommandHandlerBlock(args) {
14254
14355
  const {
14255
14356
  entityName,
14256
14357
  entitySetName,
14257
14358
  primaryIdAttribute,
14258
14359
  itemsExpr,
14259
- selectionExpr
14360
+ selectionExpr,
14361
+ exportColumnsLiteral,
14362
+ createColumnsLiteral
14260
14363
  } = args;
14364
+ const exportCase = exportColumnsLiteral ? `{
14365
+ const exportColumns = [${exportColumnsLiteral}];
14366
+ const rowsToExport = (indices.length ? indices.map((i) => items[i]) : items.slice())
14367
+ .filter((r): r is Record<string, unknown> => Boolean(r))
14368
+ .map((r) => {
14369
+ const formattedRow: Record<string, unknown> = {};
14370
+ for (const col of exportColumns) {
14371
+ formattedRow[col.key] = r[col.key + '@OData.Community.Display.V1.FormattedValue'] ?? r[col.key];
14372
+ }
14373
+ return formattedRow;
14374
+ });
14375
+ exportToFile(rowsToExport, exportColumns, 'csv', generateDefaultFilename(${JSON.stringify(entityName + "-export")}));
14376
+ return;
14377
+ }` : `{
14378
+ console.warn('[grid] Export to Excel is not yet wired in generated forms.');
14379
+ return;
14380
+ }`;
14381
+ const createStateBlock = createColumnsLiteral ? `
14382
+ const gridCreateMutation = useCreateRecord<Record<string, unknown>>('${entitySetName}');
14383
+ const [pendingCreate, setPendingCreate] = React.useState(false);
14384
+ const [createValues, setCreateValues] = React.useState<Record<string, unknown>>({});
14385
+ const [createError, setCreateError] = React.useState<string | null>(null);
14386
+ // Derived from the grid's columns; lookups/images/system fields are excluded by
14387
+ // the emitter. The payload is best-effort and validated server-side (errors show
14388
+ // in \`createError\`). NOTE: against a mock/standalone host (no Dataverse token),
14389
+ // MockApiService.createRecord no-ops and resolves \u2014 the dialog closes as if it
14390
+ // succeeded but nothing is created. Xrm is the primary path in a model-driven host.
14391
+ const createFields = React.useMemo<Array<{ key: string; label: string; kind: 'text' | 'number' | 'boolean' | 'optionset' | 'date'; inputType?: string; options?: Array<{ key: number; text: string }> }>>(
14392
+ () => [${createColumnsLiteral}],
14393
+ [],
14394
+ );
14395
+ const submitCreate = React.useCallback(async () => {
14396
+ setCreateError(null);
14397
+ const payload: Record<string, unknown> = {};
14398
+ for (const f of createFields) {
14399
+ const raw = createValues[f.key];
14400
+ if (raw === undefined || raw === null || raw === '') continue;
14401
+ payload[f.key] = (f.kind === 'number' || f.kind === 'optionset') ? Number(raw) : raw;
14402
+ }
14403
+ if (Object.keys(payload).length === 0) {
14404
+ setCreateError('Enter at least one value before creating.');
14405
+ return;
14406
+ }
14407
+ try {
14408
+ await gridCreateMutation.mutateAsync(payload);
14409
+ setPendingCreate(false);
14410
+ setCreateValues({});
14411
+ } catch (err) {
14412
+ setCreateError((err as Error)?.message ?? 'Create failed.');
14413
+ }
14414
+ }, [createValues, gridCreateMutation, createFields]);` : "";
14415
+ const newCase = createColumnsLiteral ? `{
14416
+ if (xrm?.Navigation?.openForm) {
14417
+ await xrm.Navigation.openForm({ entityName: '${entityName}', useQuickCreateForm: false });
14418
+ await refreshGrid();
14419
+ } else {
14420
+ setCreateError(null);
14421
+ setCreateValues({});
14422
+ setPendingCreate(true);
14423
+ }
14424
+ return;
14425
+ }` : `{
14426
+ if (xrm?.Navigation?.openForm) {
14427
+ await xrm.Navigation.openForm({ entityName: '${entityName}', useQuickCreateForm: false });
14428
+ await refreshGrid();
14429
+ } else {
14430
+ console.warn('[grid] New action requires Xrm.Navigation.openForm; not available in this host.');
14431
+ }
14432
+ return;
14433
+ }`;
14261
14434
  return `
14262
14435
  const queryClient = useQueryClient();
14263
14436
  const gridUpdateMutation = useUpdateRecord<Record<string, unknown>>('${entitySetName}');
@@ -14281,7 +14454,7 @@ function buildGridCommandHandlerBlock(args) {
14281
14454
  } catch (err) {
14282
14455
  setDeleteError((err as Error)?.message ?? 'Delete failed.');
14283
14456
  }
14284
- }, [pendingDelete, gridDeleteMutation]);
14457
+ }, [pendingDelete, gridDeleteMutation]);${createStateBlock}
14285
14458
  const handleGridCommand = React.useCallback(async (actionType: string, overrideRowIndex?: number) => {
14286
14459
  const xrm = (typeof window !== 'undefined' ? (window as unknown as { Xrm?: any }).Xrm : undefined);
14287
14460
  const items = ${itemsExpr} as ReadonlyArray<Record<string, unknown>>;
@@ -14306,15 +14479,7 @@ function buildGridCommandHandlerBlock(args) {
14306
14479
  return true;
14307
14480
  };
14308
14481
  switch (actionType) {
14309
- case 'new': {
14310
- if (xrm?.Navigation?.openForm) {
14311
- await xrm.Navigation.openForm({ entityName: '${entityName}', useQuickCreateForm: false });
14312
- await refreshGrid();
14313
- } else {
14314
- console.warn('[grid] New action requires Xrm.Navigation.openForm; not available in this host.');
14315
- }
14316
- return;
14317
- }
14482
+ case 'new': ${newCase}
14318
14483
  case 'addExisting': {
14319
14484
  if (xrm?.Navigation?.openForm) {
14320
14485
  await xrm.Navigation.openForm({ entityName: '${entityName}' });
@@ -14359,10 +14524,7 @@ function buildGridCommandHandlerBlock(args) {
14359
14524
  await refreshGrid();
14360
14525
  return;
14361
14526
  }
14362
- case 'export': {
14363
- console.warn('[grid] Export to Excel is not yet wired in generated forms.');
14364
- return;
14365
- }
14527
+ case 'export': ${exportCase}
14366
14528
  case 'bulkEdit': {
14367
14529
  if (!requireSel('bulk edit')) return;
14368
14530
  if (xrm?.Navigation?.openBulkEditForm) {
@@ -14379,7 +14541,7 @@ function buildGridCommandHandlerBlock(args) {
14379
14541
  }, [${itemsExpr}, ${selectionExpr}, gridUpdateMutation, refreshGrid]);`;
14380
14542
  }
14381
14543
  function buildRowCommandsLiteral(contextMenuItems) {
14382
- return contextMenuItems.filter((ci) => (ci.actionType ?? "custom") !== "custom").map(
14544
+ return contextMenuItems.filter((ci) => (ci.actionType ?? "custom") !== "custom").filter((ci) => ci.actionType !== "new").map(
14383
14545
  (ci) => `{ key: ${JSON.stringify(ci.id)}, text: ${JSON.stringify(ci.text)}` + (ci.iconName ? `, iconName: ${JSON.stringify(ci.iconName)}` : "") + `, onClick: (item: Record<string, unknown>) => { void handleGridCommand(${JSON.stringify(ci.actionType)}, (item as { __rowIndex?: number }).__rowIndex); } }`
14384
14546
  ).join(",\n ");
14385
14547
  }
@@ -14415,6 +14577,68 @@ function buildGridDeleteDialogJsx(entityName) {
14415
14577
  </DialogFooter>
14416
14578
  </Dialog>`;
14417
14579
  }
14580
+ function buildGridCreateDialogJsx(entityName) {
14581
+ return `
14582
+ <Dialog
14583
+ hidden={!pendingCreate}
14584
+ onDismiss={() => { if (!gridCreateMutation.isPending) setPendingCreate(false); }}
14585
+ dialogContentProps={{
14586
+ type: DialogType.normal,
14587
+ title: ${JSON.stringify("Create " + entityName)},
14588
+ }}
14589
+ modalProps={{ isBlocking: true }}
14590
+ >
14591
+ <div style={{ display: 'flex', flexDirection: 'column', gap: 10 }}>
14592
+ {createFields.map((f) => {
14593
+ if (f.kind === 'boolean') {
14594
+ return (
14595
+ <Checkbox
14596
+ key={f.key}
14597
+ label={f.label}
14598
+ checked={createValues[f.key] === true}
14599
+ onChange={(_ev, checked) => setCreateValues((prev) => ({ ...prev, [f.key]: !!checked }))}
14600
+ />
14601
+ );
14602
+ }
14603
+ if (f.kind === 'optionset') {
14604
+ return (
14605
+ <Dropdown
14606
+ key={f.key}
14607
+ label={f.label}
14608
+ options={f.options ?? []}
14609
+ selectedKey={(createValues[f.key] as number | undefined) ?? null}
14610
+ onChange={(_ev, opt) => setCreateValues((prev) => ({ ...prev, [f.key]: opt?.key }))}
14611
+ />
14612
+ );
14613
+ }
14614
+ return (
14615
+ <TextField
14616
+ key={f.key}
14617
+ label={f.label}
14618
+ type={f.inputType ?? 'text'}
14619
+ value={createValues[f.key] == null ? '' : String(createValues[f.key])}
14620
+ onChange={(_ev, val) => setCreateValues((prev) => ({ ...prev, [f.key]: val }))}
14621
+ />
14622
+ );
14623
+ })}
14624
+ </div>
14625
+ {createError && (
14626
+ <div style={{ color: '#a4262c', fontSize: 12, marginTop: 8 }}>{createError}</div>
14627
+ )}
14628
+ <DialogFooter>
14629
+ <PrimaryButton
14630
+ onClick={submitCreate}
14631
+ text={gridCreateMutation.isPending ? 'Creating\u2026' : 'Create'}
14632
+ disabled={gridCreateMutation.isPending}
14633
+ />
14634
+ <DefaultButton
14635
+ onClick={() => setPendingCreate(false)}
14636
+ text="Cancel"
14637
+ disabled={gridCreateMutation.isPending}
14638
+ />
14639
+ </DialogFooter>
14640
+ </Dialog>`;
14641
+ }
14418
14642
  function buildV9RenderCell(col) {
14419
14643
  const safeFieldName = JSON.stringify(col.fieldName);
14420
14644
  switch (col.rendererType) {
@@ -14792,6 +15016,8 @@ function generateLinkedSubgrid(gridDef, entityName, imports, library = "fluent-v
14792
15016
  // need the dispatcher in scope even when no wired commandBarItems exist.
14793
15017
  !!gridDef.toolbar?.showRefresh)
14794
15018
  );
15019
+ const enableExport = _handlerAvailable && (showExport || barItems.some((ci) => ci.actionType === "export") || contextMenuItems.some((ci) => ci.actionType === "export"));
15020
+ const enableCreate = _handlerAvailable && barItems.some((ci) => ci.actionType === "new");
14795
15021
  if (gridDef.showCommandBar && (barItems.length > 0 || showSearch || hasToolbarIcons)) {
14796
15022
  if (showSearch) imports.add("SearchBox");
14797
15023
  imports.add("DefaultButton");
@@ -14843,10 +15069,12 @@ function generateLinkedSubgrid(gridDef, entityName, imports, library = "fluent-v
14843
15069
  toolbarIcons.push(
14844
15070
  `<IconButton iconProps={{ iconName: 'ColumnOptions' }} title="Column chooser" ariaLabel="Column chooser" />`
14845
15071
  );
14846
- if (showExport)
15072
+ if (showExport) {
15073
+ const exportOnClick = _handlerAvailable ? ` onClick={() => { void handleGridCommand('export'); }}` : "";
14847
15074
  toolbarIcons.push(
14848
- `<IconButton iconProps={{ iconName: 'Download' }} title="Export" ariaLabel="Export" />`
15075
+ `<IconButton iconProps={{ iconName: 'Download' }} title="Export" ariaLabel="Export"${exportOnClick} />`
14849
15076
  );
15077
+ }
14850
15078
  if (showRefresh) {
14851
15079
  const refreshOnClick = _handlerAvailable ? ` onClick={() => { void handleGridCommand('refresh'); }}` : "";
14852
15080
  toolbarIcons.push(
@@ -14870,6 +15098,9 @@ function generateLinkedSubgrid(gridDef, entityName, imports, library = "fluent-v
14870
15098
  const childGridDef = gridDef.nestedGridId ? _currentGridCustomizers.find((g) => g.id === gridDef.nestedGridId) : void 0;
14871
15099
  const isGridKitNested = !!childGridDef;
14872
15100
  const visibleCols = [...gridDef.columns].filter((c) => c.isVisible).sort((a, b) => a.order - b.order);
15101
+ const exportColumnsLiteral = enableExport ? buildExportColumnsLiteral(visibleCols) : "";
15102
+ const primaryIdAttr = resolvePrimaryIdAttribute(gridDef);
15103
+ const createColumnsLiteral = enableCreate ? buildCreateFields(visibleCols, primaryIdAttr) : "";
14873
15104
  visibleCols.filter((c) => c.rendererType !== "text");
14874
15105
  const colEntries = isGridKitNested ? "" : generateColumnEntries(visibleCols, library);
14875
15106
  const selectionMode = gridDef.selectionMode === "multiple" ? "SelectionMode.multiple" : gridDef.selectionMode === "single" ? "SelectionMode.single" : "SelectionMode.none";
@@ -14935,19 +15166,27 @@ ${childEntries.join(",\n")},
14935
15166
  entitySetName: gridDef.dataSource.entitySetName,
14936
15167
  primaryIdAttribute: resolvePrimaryIdAttribute(gridDef),
14937
15168
  itemsExpr: "keyedItems",
14938
- selectionExpr: "selectedIndices"
15169
+ selectionExpr: "selectedIndices",
15170
+ exportColumnsLiteral,
15171
+ createColumnsLiteral
14939
15172
  }) : "";
14940
15173
  const ngHasDelete = (gridDef.commandBarItems ?? []).some(
14941
15174
  (ci) => ci.actionType === "delete"
14942
15175
  );
14943
15176
  const ngDeleteDialogJsx = ngHasDelete && ngHandlerBlock !== "" ? buildGridDeleteDialogJsx(gridDef.dataSource?.entityName ?? entityName) : "";
14944
- if (ngDeleteDialogJsx) {
15177
+ const ngCreateDialogJsx = ngHandlerBlock !== "" && createColumnsLiteral ? buildGridCreateDialogJsx(gridDef.dataSource?.entityName ?? entityName) : "";
15178
+ if (ngDeleteDialogJsx || ngCreateDialogJsx) {
14945
15179
  imports.add("Dialog");
14946
15180
  imports.add("DialogType");
14947
15181
  imports.add("DialogFooter");
14948
15182
  imports.add("PrimaryButton");
14949
15183
  imports.add("DefaultButton");
14950
15184
  }
15185
+ if (ngCreateDialogJsx) {
15186
+ imports.add("TextField");
15187
+ imports.add("Dropdown");
15188
+ imports.add("Checkbox");
15189
+ }
14951
15190
  const ngRowCommandsLiteral = _handlerAvailable && (ngHasCommandBar || ngHasContextMenu && useLiveNested) ? buildRowCommandsLiteral(contextMenuItems) : "";
14952
15191
  const ngToolbarWired = (gridDef.commandBarItems ?? []).some(
14953
15192
  (ci) => (ci.actionType ?? "custom") !== "custom"
@@ -14961,9 +15200,18 @@ ${childEntries.join(",\n")},
14961
15200
  const ngDataverseHooks = [
14962
15201
  ...useLiveNested ? ["useDataverseQuery"] : [],
14963
15202
  ...useLiveNested && ngNeedsUpdate || ngHasHandler ? ["useUpdateRecord"] : [],
14964
- ...useLiveNested && ngNeedsDelete || ngHasHandler ? ["useDeleteRecord"] : []
15203
+ ...useLiveNested && ngNeedsDelete || ngHasHandler ? ["useDeleteRecord"] : [],
15204
+ // Create mutation only when the create dialog is wired (the `new` case's
15205
+ // non-Xrm fallback) — gated like the export util import to stay byte-stable
15206
+ // for non-create handler grids.
15207
+ ...ngHasHandler && createColumnsLiteral ? ["useCreateRecord"] : []
15208
+ ];
15209
+ const ngCommandBarImports = [
15210
+ ...useLiveNested && ngNeedsDelete || ngHasHandler ? [`import { useQueryClient } from '@tanstack/react-query';`] : [],
15211
+ // Export case (shared dispatcher → both nested + focused-view) calls
15212
+ // exportToFile/generateDefaultFilename when export is wired.
15213
+ ...ngHasHandler && enableExport ? [`import { exportToFile, generateDefaultFilename } from '../lib/grid-kit/utils';`] : []
14965
15214
  ];
14966
- const ngCommandBarImports = useLiveNested && ngNeedsDelete || ngHasHandler ? [`import { useQueryClient } from '@tanstack/react-query';`] : [];
14967
15215
  const ngParentSelectionMode = ngHasCommandBar ? gridDef.selectionMode === "single" ? "single" : "multiple" : gridDef.selectionMode ?? "none";
14968
15216
  const ngChildSaveBack = Boolean(
14969
15217
  useLiveNested && live && (childGridDef.isEditable ?? false)
@@ -15012,6 +15260,7 @@ ${childEntries.join(",\n")},
15012
15260
  commandBarBlock: gridCommandBarBlock,
15013
15261
  handlerBlock: ngHandlerBlock,
15014
15262
  deleteDialogJsx: ngDeleteDialogJsx,
15263
+ createDialogJsx: ngCreateDialogJsx,
15015
15264
  hasSelection: ngHasCommandBar,
15016
15265
  hasHandler: ngHandlerBlock !== "",
15017
15266
  dataverseHooks: ngDataverseHooks,
@@ -15067,6 +15316,7 @@ ${childEntries.join(",\n")},
15067
15316
  commandBarBlock: gridCommandBarBlock,
15068
15317
  handlerBlock: ngHandlerBlock,
15069
15318
  deleteDialogJsx: ngDeleteDialogJsx,
15319
+ createDialogJsx: ngCreateDialogJsx,
15070
15320
  hasSelection: ngHasCommandBar,
15071
15321
  dataverseHooks: ngDataverseHooks,
15072
15322
  commandBarImports: ngCommandBarImports,
@@ -15147,11 +15397,14 @@ ${childEntries.join(",\n")},
15147
15397
  entitySetName: gridDef.dataSource.entitySetName,
15148
15398
  primaryIdAttribute: resolvePrimaryIdAttribute(gridDef),
15149
15399
  itemsExpr: "rows",
15150
- selectionExpr: "selectedIndices"
15400
+ selectionExpr: "selectedIndices",
15401
+ exportColumnsLiteral,
15402
+ createColumnsLiteral
15151
15403
  }) : "";
15152
15404
  const cardSelectedIndicesState = cardEmitHandler ? "\n const [selectedIndices, setSelectedIndices] = React.useState<Set<number>>(new Set());" : "";
15153
15405
  const cardRowCommandsLiteral = cardEmitRowCommands ? buildRowCommandsLiteral(contextMenuItems) : "";
15154
15406
  const cardDeleteDialogJsx = cardEmitRowCommands && flatHasDelete || cardEmitToolbar && flatBarHasDelete ? buildGridDeleteDialogJsx(gridDef.dataSource?.entityName ?? entityName) : "";
15407
+ const cardCreateDialogJsx = cardEmitHandler && createColumnsLiteral ? buildGridCreateDialogJsx(gridDef.dataSource?.entityName ?? entityName) : "";
15155
15408
  const cardCommandBarBlock = cardEmitToolbar ? gridCommandBarBlock : "";
15156
15409
  const cardSelectionModeAttr = cardEmitToolbar ? flatSelectionModeAttr : "";
15157
15410
  const cardOnSelectionChangedAttr = cardEmitToolbar ? flatOnSelectionChangedAttr : "";
@@ -15170,20 +15423,26 @@ ${childEntries.join(",\n")},
15170
15423
  handlerBlock: cardHandlerBlock,
15171
15424
  rowCommandsLiteral: cardRowCommandsLiteral,
15172
15425
  deleteDialogJsx: cardDeleteDialogJsx,
15426
+ createDialogJsx: cardCreateDialogJsx,
15173
15427
  commandBarBlock: cardCommandBarBlock,
15174
15428
  selectionModeAttr: cardSelectionModeAttr,
15175
15429
  onSelectionChangedAttr: cardOnSelectionChangedAttr
15176
15430
  })
15177
15431
  ];
15178
15432
  _usesGridKit = true;
15179
- const cardDataverseImport = cardEmitHandler ? `import { useDataverseQuery, useUpdateRecord, useDeleteRecord } from '../lib/dataverse';` : `import { useDataverseQuery } from '../lib/dataverse';`;
15180
- if (cardDeleteDialogJsx) {
15433
+ const cardDataverseImport = cardEmitHandler ? `import { useDataverseQuery, useUpdateRecord, useDeleteRecord${createColumnsLiteral ? ", useCreateRecord" : ""} } from '../lib/dataverse';` : `import { useDataverseQuery } from '../lib/dataverse';`;
15434
+ if (cardDeleteDialogJsx || cardCreateDialogJsx) {
15181
15435
  imports.add("Dialog");
15182
15436
  imports.add("DialogType");
15183
15437
  imports.add("DialogFooter");
15184
15438
  imports.add("PrimaryButton");
15185
15439
  imports.add("DefaultButton");
15186
15440
  }
15441
+ if (cardCreateDialogJsx) {
15442
+ imports.add("TextField");
15443
+ imports.add("Dropdown");
15444
+ imports.add("Checkbox");
15445
+ }
15187
15446
  return {
15188
15447
  imports,
15189
15448
  v9Imports,
@@ -15191,7 +15450,10 @@ ${childEntries.join(",\n")},
15191
15450
  extraImports: cardIsLive ? cardEmitHandler ? [
15192
15451
  cardDataverseImport,
15193
15452
  `import { useQueryClient } from '@tanstack/react-query';`,
15194
- `import { CardGrid, createCellRegistry, type ColumnDef } from '../lib/grid-kit';`
15453
+ `import { CardGrid, createCellRegistry, type ColumnDef } from '../lib/grid-kit';`,
15454
+ // Export case calls exportToFile/generateDefaultFilename (cardEmitHandler
15455
+ // here; the export case is only real when enableExport).
15456
+ ...enableExport ? [`import { exportToFile, generateDefaultFilename } from '../lib/grid-kit/utils';`] : []
15195
15457
  ] : [
15196
15458
  `import { useDataverseQuery } from '../lib/dataverse';`,
15197
15459
  `import { CardGrid, createCellRegistry, type ColumnDef } from '../lib/grid-kit';`
@@ -15229,11 +15491,14 @@ ${childEntries.join(",\n")},
15229
15491
  entitySetName: gridDef.dataSource.entitySetName,
15230
15492
  primaryIdAttribute: resolvePrimaryIdAttribute(gridDef),
15231
15493
  itemsExpr: "rows",
15232
- selectionExpr: "selectedIndices"
15494
+ selectionExpr: "selectedIndices",
15495
+ exportColumnsLiteral,
15496
+ createColumnsLiteral
15233
15497
  }) : "";
15234
15498
  const roSelectedIndicesState = roEmitHandler ? "\n const [selectedIndices, setSelectedIndices] = React.useState<Set<number>>(new Set());" : "";
15235
15499
  const roRowCommandsLiteral = roEmitRowCommands ? buildRowCommandsLiteral(contextMenuItems) : "";
15236
15500
  const roDeleteDialogJsx = roEmitRowCommands && flatHasDelete ? buildGridDeleteDialogJsx(gridDef.dataSource?.entityName ?? entityName) : "";
15501
+ const roCreateDialogJsx = roEmitHandler && createColumnsLiteral ? buildGridCreateDialogJsx(gridDef.dataSource?.entityName ?? entityName) : "";
15237
15502
  const roCommandBarBlock = roEmitToolbar ? gridCommandBarBlock : "";
15238
15503
  const helperComponents = [
15239
15504
  buildLiveReadOnlySubgridBody({
@@ -15252,18 +15517,24 @@ ${childEntries.join(",\n")},
15252
15517
  handlerBlock: roHandlerBlock,
15253
15518
  rowCommandsLiteral: roRowCommandsLiteral,
15254
15519
  deleteDialogJsx: roDeleteDialogJsx,
15520
+ createDialogJsx: roCreateDialogJsx,
15255
15521
  commandBarBlock: roCommandBarBlock
15256
15522
  })
15257
15523
  ];
15258
15524
  if (!isV9) _usesGridKit = true;
15259
- const roDataverseImport = roEmitHandler ? `import { useDataverseQuery, useUpdateRecord, useDeleteRecord } from '../lib/dataverse';` : `import { useDataverseQuery } from '../lib/dataverse';`;
15260
- if (roDeleteDialogJsx) {
15525
+ const roDataverseImport = roEmitHandler ? `import { useDataverseQuery, useUpdateRecord, useDeleteRecord${createColumnsLiteral ? ", useCreateRecord" : ""} } from '../lib/dataverse';` : `import { useDataverseQuery } from '../lib/dataverse';`;
15526
+ if (roDeleteDialogJsx || roCreateDialogJsx) {
15261
15527
  imports.add("Dialog");
15262
15528
  imports.add("DialogType");
15263
15529
  imports.add("DialogFooter");
15264
15530
  imports.add("PrimaryButton");
15265
15531
  imports.add("DefaultButton");
15266
15532
  }
15533
+ if (roCreateDialogJsx) {
15534
+ imports.add("TextField");
15535
+ imports.add("Dropdown");
15536
+ imports.add("Checkbox");
15537
+ }
15267
15538
  return {
15268
15539
  imports,
15269
15540
  v9Imports,
@@ -15271,7 +15542,8 @@ ${childEntries.join(",\n")},
15271
15542
  extraImports: isV9 ? [`import { useDataverseQuery } from '../lib/dataverse';`] : roEmitHandler ? [
15272
15543
  roDataverseImport,
15273
15544
  `import { useQueryClient } from '@tanstack/react-query';`,
15274
- `import { ReadOnlyGrid, createCellRegistry, type ColumnDef } from '../lib/grid-kit';`
15545
+ `import { ReadOnlyGrid, createCellRegistry, type ColumnDef } from '../lib/grid-kit';`,
15546
+ ...enableExport ? [`import { exportToFile, generateDefaultFilename } from '../lib/grid-kit/utils';`] : []
15275
15547
  ] : [
15276
15548
  `import { useDataverseQuery } from '../lib/dataverse';`,
15277
15549
  `import { ReadOnlyGrid, createCellRegistry, type ColumnDef } from '../lib/grid-kit';`
@@ -15331,11 +15603,14 @@ const ${liveWrapperName}: React.FC = () => {
15331
15603
  entitySetName: gridDef.dataSource.entitySetName,
15332
15604
  primaryIdAttribute: resolvePrimaryIdAttribute(gridDef),
15333
15605
  itemsExpr: "keyedItems",
15334
- selectionExpr: "selectedIndices"
15606
+ selectionExpr: "selectedIndices",
15607
+ exportColumnsLiteral,
15608
+ createColumnsLiteral
15335
15609
  }) : "";
15336
15610
  const edSelectedIndicesState = edEmitHandler ? "\n const [selectedIndices, setSelectedIndices] = React.useState<Set<number>>(new Set());" : "";
15337
15611
  const edRowCommandsLiteral = edEmitRowCommands ? buildRowCommandsLiteral(contextMenuItems) : "";
15338
15612
  const edDeleteDialogJsx = edEmitRowCommands && flatHasDelete || edEmitToolbar && flatBarHasDelete ? buildGridDeleteDialogJsx(gridDef.dataSource?.entityName ?? entityName) : "";
15613
+ const edCreateDialogJsx = edEmitHandler && createColumnsLiteral ? buildGridCreateDialogJsx(gridDef.dataSource?.entityName ?? entityName) : "";
15339
15614
  const edCommandBarBlock = edEmitToolbar ? gridCommandBarBlock : "";
15340
15615
  const edSelectionModeAttr = edEmitToolbar ? flatSelectionModeAttr : "";
15341
15616
  const edOnSelectionChangedAttr = edEmitToolbar ? flatOnSelectionChangedAttr : "";
@@ -15352,6 +15627,7 @@ const ${liveWrapperName}: React.FC = () => {
15352
15627
  handlerBlock: edHandlerBlock,
15353
15628
  rowCommandsLiteral: edRowCommandsLiteral,
15354
15629
  deleteDialogJsx: edDeleteDialogJsx,
15630
+ createDialogJsx: edCreateDialogJsx,
15355
15631
  commandBarBlock: edCommandBarBlock,
15356
15632
  selectionModeAttr: edSelectionModeAttr,
15357
15633
  onSelectionChangedAttr: edOnSelectionChangedAttr
@@ -15363,18 +15639,24 @@ const ${liveWrapperName}: React.FC = () => {
15363
15639
  const jsx3 = editableUsesLiveData && liveWrapperName ? `{/* Subgrid: ${safeEntityName} \u2014 ${safeGridName} (editable, live) */}${todoComment}
15364
15640
  <${liveWrapperName} />` : `{/* Subgrid: ${safeEntityName} \u2014 ${safeGridName} (editable) */}${todoComment}
15365
15641
  <${componentName} items={${itemsLiteral}} />`;
15366
- const edDataverseImport = edEmitHandler ? `import { useDataverseQuery, useUpdateRecord, useDeleteRecord } from '../lib/dataverse';` : `import { useDataverseQuery, useUpdateRecord } from '../lib/dataverse';`;
15367
- if (edDeleteDialogJsx) {
15642
+ const edDataverseImport = edEmitHandler ? `import { useDataverseQuery, useUpdateRecord, useDeleteRecord${createColumnsLiteral ? ", useCreateRecord" : ""} } from '../lib/dataverse';` : `import { useDataverseQuery, useUpdateRecord } from '../lib/dataverse';`;
15643
+ if (edDeleteDialogJsx || edCreateDialogJsx) {
15368
15644
  imports.add("Dialog");
15369
15645
  imports.add("DialogType");
15370
15646
  imports.add("DialogFooter");
15371
15647
  imports.add("PrimaryButton");
15372
15648
  imports.add("DefaultButton");
15373
15649
  }
15650
+ if (edCreateDialogJsx) {
15651
+ imports.add("TextField");
15652
+ imports.add("Dropdown");
15653
+ imports.add("Checkbox");
15654
+ }
15374
15655
  const extraImports2 = editableUsesLiveData ? edEmitHandler ? [
15375
15656
  edDataverseImport,
15376
15657
  `import { useQueryClient } from '@tanstack/react-query';`,
15377
- `import { DataGrid, createCellRegistry, type ColumnDef } from '../lib/grid-kit';`
15658
+ `import { DataGrid, createCellRegistry, type ColumnDef } from '../lib/grid-kit';`,
15659
+ ...enableExport ? [`import { exportToFile, generateDefaultFilename } from '../lib/grid-kit/utils';`] : []
15378
15660
  ] : [
15379
15661
  `import { useDataverseQuery, useUpdateRecord } from '../lib/dataverse';`,
15380
15662
  `import { DataGrid, createCellRegistry, type ColumnDef } from '../lib/grid-kit';`
package/dist/index.mjs CHANGED
@@ -12790,6 +12790,7 @@ function buildV8Body(args) {
12790
12790
  handlerBlock = "",
12791
12791
  rowCommandsLiteral = "",
12792
12792
  deleteDialogJsx = "",
12793
+ createDialogJsx = "",
12793
12794
  commandBarBlock = "",
12794
12795
  selectionModeAttr = "",
12795
12796
  onSelectionChangedAttr = ""
@@ -12816,7 +12817,7 @@ const ${componentName}: React.FC<{ items: Record<string, unknown>[] }> = ({ item
12816
12817
  editable
12817
12818
  editTrigger="click"
12818
12819
  getKey={(it) => String((it as { __rowIndex?: number }).__rowIndex ?? '')}${selectionModeAttr}${onSelectionChangedAttr}${onValueChangeAttr}${rowCommandsAttr}
12819
- />${editableAggregate}${editablePagination}${saveStatusJsx}${deleteDialogJsx}
12820
+ />${editableAggregate}${editablePagination}${saveStatusJsx}${deleteDialogJsx}${createDialogJsx}
12820
12821
  </div>
12821
12822
  );
12822
12823
  };`;
@@ -12869,6 +12870,7 @@ function buildLiveReadOnlyV8Body(args) {
12869
12870
  handlerBlock = "",
12870
12871
  rowCommandsLiteral = "",
12871
12872
  deleteDialogJsx = "",
12873
+ createDialogJsx = "",
12872
12874
  commandBarBlock = ""
12873
12875
  } = args;
12874
12876
  const rowCommandsAttr = rowCommandsLiteral ? `
@@ -12897,7 +12899,7 @@ const ${helperName}: React.FC = () => {
12897
12899
  ] as ColumnDef[]}
12898
12900
  registry={registry}
12899
12901
  getKey={(it) => String((it as { __rowIndex?: number }).__rowIndex ?? '')}${rowCommandsAttr}
12900
- />${flatAggregateInner}${flatPaginationInner}${deleteDialogJsx}
12902
+ />${flatAggregateInner}${flatPaginationInner}${deleteDialogJsx}${createDialogJsx}
12901
12903
  </div>
12902
12904
  );
12903
12905
  };`;
@@ -12943,6 +12945,7 @@ function buildCardV8LiveBody(args) {
12943
12945
  handlerBlock = "",
12944
12946
  rowCommandsLiteral = "",
12945
12947
  deleteDialogJsx = "",
12948
+ createDialogJsx = "",
12946
12949
  commandBarBlock = "",
12947
12950
  selectionModeAttr = "",
12948
12951
  onSelectionChangedAttr = ""
@@ -12974,7 +12977,7 @@ const ${helperName}: React.FC = () => {
12974
12977
  registry={registry}
12975
12978
  getKey={(it) => String((it as { __rowIndex?: number }).__rowIndex ?? '')}${selectionModeAttr}${onSelectionChangedAttr}
12976
12979
  card={{ ${cardConfigLiteral} }}${rowCommandsAttr}
12977
- />${flatAggregateInner}${flatPaginationInner}${deleteDialogJsx}
12980
+ />${flatAggregateInner}${flatPaginationInner}${deleteDialogJsx}${createDialogJsx}
12978
12981
  </div>
12979
12982
  );
12980
12983
  };`;
@@ -13300,6 +13303,7 @@ function buildNestedSubgridBody(args) {
13300
13303
  commandBarBlock,
13301
13304
  handlerBlock,
13302
13305
  deleteDialogJsx,
13306
+ createDialogJsx,
13303
13307
  hasSelection,
13304
13308
  hasHandler,
13305
13309
  dataverseHooks,
@@ -13355,7 +13359,7 @@ const ${componentName}: React.FC<{ items: Record<string, unknown>[] }> = ({ item
13355
13359
  calloutMaxRows: ${calloutMaxRows},
13356
13360
  hoverDelay: ${hoverDelay},${childEditableLiteral}
13357
13361
  }}
13358
- />${deleteDialogJsx}${childSaveStatusJsx}
13362
+ />${deleteDialogJsx}${createDialogJsx}${childSaveStatusJsx}
13359
13363
  </div>
13360
13364
  );
13361
13365
  };`;
@@ -13398,6 +13402,7 @@ function buildFocusedViewSubgridBody(args) {
13398
13402
  commandBarBlock,
13399
13403
  handlerBlock,
13400
13404
  deleteDialogJsx,
13405
+ createDialogJsx,
13401
13406
  hasSelection,
13402
13407
  hasHandler,
13403
13408
  dataverseHooks,
@@ -13451,7 +13456,7 @@ const ${componentName}: React.FC<{ items: Record<string, unknown>[] }> = ({ item
13451
13456
  childSelectionMode: ${JSON.stringify(childSelectionMode)},
13452
13457
  childSelectionGating: ${JSON.stringify(childSelectionGating)},${childEditableLiteral}
13453
13458
  }}
13454
- />${deleteDialogJsx}${childSaveStatusJsx}
13459
+ />${deleteDialogJsx}${createDialogJsx}${childSaveStatusJsx}
13455
13460
  </div>
13456
13461
  );
13457
13462
  };`;
@@ -14230,14 +14235,182 @@ function effectiveMinSelection(item) {
14230
14235
  if (typeof item.minSelectionCount === "number") return item.minSelectionCount;
14231
14236
  return DEFAULT_MIN_SEL_BY_ACTION[item.actionType ?? "custom"] ?? 0;
14232
14237
  }
14238
+ function buildExportColumnsLiteral(cols) {
14239
+ return cols.map(
14240
+ (c) => `{ key: ${JSON.stringify(c.fieldName)}, name: ${JSON.stringify(c.displayName ?? c.fieldName)} }`
14241
+ ).join(", ");
14242
+ }
14243
+ var CREATE_FIELD_DENYLIST = /* @__PURE__ */ new Set([
14244
+ "createdon",
14245
+ "createdby",
14246
+ "createdonbehalfby",
14247
+ "modifiedon",
14248
+ "modifiedby",
14249
+ "modifiedonbehalfby",
14250
+ "owningbusinessunit",
14251
+ "owningteam",
14252
+ "owninguser",
14253
+ "ownerid",
14254
+ "versionnumber",
14255
+ "overriddencreatedon",
14256
+ "importsequencenumber",
14257
+ "timezoneruleversionnumber",
14258
+ "utcconversiontimezonecode",
14259
+ "statecode",
14260
+ "statuscode"
14261
+ ]);
14262
+ function buildCreateFields(cols, primaryIdAttribute) {
14263
+ const primaryLower = (primaryIdAttribute ?? "").toLowerCase();
14264
+ const entries = [];
14265
+ for (const c of cols) {
14266
+ if (!c.fieldName) continue;
14267
+ const logical = (c.attributeLogicalName ?? c.fieldName).toLowerCase();
14268
+ if (logical === primaryLower) continue;
14269
+ if (c.isLocked) continue;
14270
+ if (c.dataType === "lookup" || c.dataType === "image") continue;
14271
+ if (CREATE_FIELD_DENYLIST.has(logical)) continue;
14272
+ const label = c.displayName ?? c.fieldName;
14273
+ let kind = "text";
14274
+ let inputType;
14275
+ let optionsLit = "";
14276
+ switch (c.dataType) {
14277
+ case "numeric":
14278
+ case "currency":
14279
+ kind = "number";
14280
+ inputType = "number";
14281
+ break;
14282
+ case "boolean":
14283
+ kind = "boolean";
14284
+ break;
14285
+ case "date":
14286
+ kind = "date";
14287
+ inputType = "date";
14288
+ break;
14289
+ case "datetime":
14290
+ kind = "date";
14291
+ inputType = "datetime-local";
14292
+ break;
14293
+ case "optionset": {
14294
+ const opts = c.rendererConfig?.options ?? [];
14295
+ if (opts.length === 0) {
14296
+ kind = "number";
14297
+ inputType = "number";
14298
+ } else {
14299
+ kind = "optionset";
14300
+ optionsLit = opts.map(
14301
+ (o) => `{ key: ${JSON.stringify(o.value)}, text: ${JSON.stringify(o.label)} }`
14302
+ ).join(", ");
14303
+ }
14304
+ break;
14305
+ }
14306
+ case "email":
14307
+ kind = "text";
14308
+ inputType = "email";
14309
+ break;
14310
+ case "phone":
14311
+ kind = "text";
14312
+ inputType = "tel";
14313
+ break;
14314
+ case "url":
14315
+ kind = "text";
14316
+ inputType = "url";
14317
+ break;
14318
+ default:
14319
+ kind = "text";
14320
+ break;
14321
+ }
14322
+ const payloadKey = c.attributeLogicalName ?? c.fieldName;
14323
+ const parts = [
14324
+ `key: ${JSON.stringify(payloadKey)}`,
14325
+ `label: ${JSON.stringify(label)}`,
14326
+ `kind: ${JSON.stringify(kind)}`
14327
+ ];
14328
+ if (inputType) parts.push(`inputType: ${JSON.stringify(inputType)}`);
14329
+ if (kind === "optionset") parts.push(`options: [${optionsLit}]`);
14330
+ entries.push(`{ ${parts.join(", ")} }`);
14331
+ }
14332
+ return entries.join(", ");
14333
+ }
14233
14334
  function buildGridCommandHandlerBlock(args) {
14234
14335
  const {
14235
14336
  entityName,
14236
14337
  entitySetName,
14237
14338
  primaryIdAttribute,
14238
14339
  itemsExpr,
14239
- selectionExpr
14340
+ selectionExpr,
14341
+ exportColumnsLiteral,
14342
+ createColumnsLiteral
14240
14343
  } = args;
14344
+ const exportCase = exportColumnsLiteral ? `{
14345
+ const exportColumns = [${exportColumnsLiteral}];
14346
+ const rowsToExport = (indices.length ? indices.map((i) => items[i]) : items.slice())
14347
+ .filter((r): r is Record<string, unknown> => Boolean(r))
14348
+ .map((r) => {
14349
+ const formattedRow: Record<string, unknown> = {};
14350
+ for (const col of exportColumns) {
14351
+ formattedRow[col.key] = r[col.key + '@OData.Community.Display.V1.FormattedValue'] ?? r[col.key];
14352
+ }
14353
+ return formattedRow;
14354
+ });
14355
+ exportToFile(rowsToExport, exportColumns, 'csv', generateDefaultFilename(${JSON.stringify(entityName + "-export")}));
14356
+ return;
14357
+ }` : `{
14358
+ console.warn('[grid] Export to Excel is not yet wired in generated forms.');
14359
+ return;
14360
+ }`;
14361
+ const createStateBlock = createColumnsLiteral ? `
14362
+ const gridCreateMutation = useCreateRecord<Record<string, unknown>>('${entitySetName}');
14363
+ const [pendingCreate, setPendingCreate] = React.useState(false);
14364
+ const [createValues, setCreateValues] = React.useState<Record<string, unknown>>({});
14365
+ const [createError, setCreateError] = React.useState<string | null>(null);
14366
+ // Derived from the grid's columns; lookups/images/system fields are excluded by
14367
+ // the emitter. The payload is best-effort and validated server-side (errors show
14368
+ // in \`createError\`). NOTE: against a mock/standalone host (no Dataverse token),
14369
+ // MockApiService.createRecord no-ops and resolves \u2014 the dialog closes as if it
14370
+ // succeeded but nothing is created. Xrm is the primary path in a model-driven host.
14371
+ const createFields = React.useMemo<Array<{ key: string; label: string; kind: 'text' | 'number' | 'boolean' | 'optionset' | 'date'; inputType?: string; options?: Array<{ key: number; text: string }> }>>(
14372
+ () => [${createColumnsLiteral}],
14373
+ [],
14374
+ );
14375
+ const submitCreate = React.useCallback(async () => {
14376
+ setCreateError(null);
14377
+ const payload: Record<string, unknown> = {};
14378
+ for (const f of createFields) {
14379
+ const raw = createValues[f.key];
14380
+ if (raw === undefined || raw === null || raw === '') continue;
14381
+ payload[f.key] = (f.kind === 'number' || f.kind === 'optionset') ? Number(raw) : raw;
14382
+ }
14383
+ if (Object.keys(payload).length === 0) {
14384
+ setCreateError('Enter at least one value before creating.');
14385
+ return;
14386
+ }
14387
+ try {
14388
+ await gridCreateMutation.mutateAsync(payload);
14389
+ setPendingCreate(false);
14390
+ setCreateValues({});
14391
+ } catch (err) {
14392
+ setCreateError((err as Error)?.message ?? 'Create failed.');
14393
+ }
14394
+ }, [createValues, gridCreateMutation, createFields]);` : "";
14395
+ const newCase = createColumnsLiteral ? `{
14396
+ if (xrm?.Navigation?.openForm) {
14397
+ await xrm.Navigation.openForm({ entityName: '${entityName}', useQuickCreateForm: false });
14398
+ await refreshGrid();
14399
+ } else {
14400
+ setCreateError(null);
14401
+ setCreateValues({});
14402
+ setPendingCreate(true);
14403
+ }
14404
+ return;
14405
+ }` : `{
14406
+ if (xrm?.Navigation?.openForm) {
14407
+ await xrm.Navigation.openForm({ entityName: '${entityName}', useQuickCreateForm: false });
14408
+ await refreshGrid();
14409
+ } else {
14410
+ console.warn('[grid] New action requires Xrm.Navigation.openForm; not available in this host.');
14411
+ }
14412
+ return;
14413
+ }`;
14241
14414
  return `
14242
14415
  const queryClient = useQueryClient();
14243
14416
  const gridUpdateMutation = useUpdateRecord<Record<string, unknown>>('${entitySetName}');
@@ -14261,7 +14434,7 @@ function buildGridCommandHandlerBlock(args) {
14261
14434
  } catch (err) {
14262
14435
  setDeleteError((err as Error)?.message ?? 'Delete failed.');
14263
14436
  }
14264
- }, [pendingDelete, gridDeleteMutation]);
14437
+ }, [pendingDelete, gridDeleteMutation]);${createStateBlock}
14265
14438
  const handleGridCommand = React.useCallback(async (actionType: string, overrideRowIndex?: number) => {
14266
14439
  const xrm = (typeof window !== 'undefined' ? (window as unknown as { Xrm?: any }).Xrm : undefined);
14267
14440
  const items = ${itemsExpr} as ReadonlyArray<Record<string, unknown>>;
@@ -14286,15 +14459,7 @@ function buildGridCommandHandlerBlock(args) {
14286
14459
  return true;
14287
14460
  };
14288
14461
  switch (actionType) {
14289
- case 'new': {
14290
- if (xrm?.Navigation?.openForm) {
14291
- await xrm.Navigation.openForm({ entityName: '${entityName}', useQuickCreateForm: false });
14292
- await refreshGrid();
14293
- } else {
14294
- console.warn('[grid] New action requires Xrm.Navigation.openForm; not available in this host.');
14295
- }
14296
- return;
14297
- }
14462
+ case 'new': ${newCase}
14298
14463
  case 'addExisting': {
14299
14464
  if (xrm?.Navigation?.openForm) {
14300
14465
  await xrm.Navigation.openForm({ entityName: '${entityName}' });
@@ -14339,10 +14504,7 @@ function buildGridCommandHandlerBlock(args) {
14339
14504
  await refreshGrid();
14340
14505
  return;
14341
14506
  }
14342
- case 'export': {
14343
- console.warn('[grid] Export to Excel is not yet wired in generated forms.');
14344
- return;
14345
- }
14507
+ case 'export': ${exportCase}
14346
14508
  case 'bulkEdit': {
14347
14509
  if (!requireSel('bulk edit')) return;
14348
14510
  if (xrm?.Navigation?.openBulkEditForm) {
@@ -14359,7 +14521,7 @@ function buildGridCommandHandlerBlock(args) {
14359
14521
  }, [${itemsExpr}, ${selectionExpr}, gridUpdateMutation, refreshGrid]);`;
14360
14522
  }
14361
14523
  function buildRowCommandsLiteral(contextMenuItems) {
14362
- return contextMenuItems.filter((ci) => (ci.actionType ?? "custom") !== "custom").map(
14524
+ return contextMenuItems.filter((ci) => (ci.actionType ?? "custom") !== "custom").filter((ci) => ci.actionType !== "new").map(
14363
14525
  (ci) => `{ key: ${JSON.stringify(ci.id)}, text: ${JSON.stringify(ci.text)}` + (ci.iconName ? `, iconName: ${JSON.stringify(ci.iconName)}` : "") + `, onClick: (item: Record<string, unknown>) => { void handleGridCommand(${JSON.stringify(ci.actionType)}, (item as { __rowIndex?: number }).__rowIndex); } }`
14364
14526
  ).join(",\n ");
14365
14527
  }
@@ -14395,6 +14557,68 @@ function buildGridDeleteDialogJsx(entityName) {
14395
14557
  </DialogFooter>
14396
14558
  </Dialog>`;
14397
14559
  }
14560
+ function buildGridCreateDialogJsx(entityName) {
14561
+ return `
14562
+ <Dialog
14563
+ hidden={!pendingCreate}
14564
+ onDismiss={() => { if (!gridCreateMutation.isPending) setPendingCreate(false); }}
14565
+ dialogContentProps={{
14566
+ type: DialogType.normal,
14567
+ title: ${JSON.stringify("Create " + entityName)},
14568
+ }}
14569
+ modalProps={{ isBlocking: true }}
14570
+ >
14571
+ <div style={{ display: 'flex', flexDirection: 'column', gap: 10 }}>
14572
+ {createFields.map((f) => {
14573
+ if (f.kind === 'boolean') {
14574
+ return (
14575
+ <Checkbox
14576
+ key={f.key}
14577
+ label={f.label}
14578
+ checked={createValues[f.key] === true}
14579
+ onChange={(_ev, checked) => setCreateValues((prev) => ({ ...prev, [f.key]: !!checked }))}
14580
+ />
14581
+ );
14582
+ }
14583
+ if (f.kind === 'optionset') {
14584
+ return (
14585
+ <Dropdown
14586
+ key={f.key}
14587
+ label={f.label}
14588
+ options={f.options ?? []}
14589
+ selectedKey={(createValues[f.key] as number | undefined) ?? null}
14590
+ onChange={(_ev, opt) => setCreateValues((prev) => ({ ...prev, [f.key]: opt?.key }))}
14591
+ />
14592
+ );
14593
+ }
14594
+ return (
14595
+ <TextField
14596
+ key={f.key}
14597
+ label={f.label}
14598
+ type={f.inputType ?? 'text'}
14599
+ value={createValues[f.key] == null ? '' : String(createValues[f.key])}
14600
+ onChange={(_ev, val) => setCreateValues((prev) => ({ ...prev, [f.key]: val }))}
14601
+ />
14602
+ );
14603
+ })}
14604
+ </div>
14605
+ {createError && (
14606
+ <div style={{ color: '#a4262c', fontSize: 12, marginTop: 8 }}>{createError}</div>
14607
+ )}
14608
+ <DialogFooter>
14609
+ <PrimaryButton
14610
+ onClick={submitCreate}
14611
+ text={gridCreateMutation.isPending ? 'Creating\u2026' : 'Create'}
14612
+ disabled={gridCreateMutation.isPending}
14613
+ />
14614
+ <DefaultButton
14615
+ onClick={() => setPendingCreate(false)}
14616
+ text="Cancel"
14617
+ disabled={gridCreateMutation.isPending}
14618
+ />
14619
+ </DialogFooter>
14620
+ </Dialog>`;
14621
+ }
14398
14622
  function buildV9RenderCell(col) {
14399
14623
  const safeFieldName = JSON.stringify(col.fieldName);
14400
14624
  switch (col.rendererType) {
@@ -14772,6 +14996,8 @@ function generateLinkedSubgrid(gridDef, entityName, imports, library = "fluent-v
14772
14996
  // need the dispatcher in scope even when no wired commandBarItems exist.
14773
14997
  !!gridDef.toolbar?.showRefresh)
14774
14998
  );
14999
+ const enableExport = _handlerAvailable && (showExport || barItems.some((ci) => ci.actionType === "export") || contextMenuItems.some((ci) => ci.actionType === "export"));
15000
+ const enableCreate = _handlerAvailable && barItems.some((ci) => ci.actionType === "new");
14775
15001
  if (gridDef.showCommandBar && (barItems.length > 0 || showSearch || hasToolbarIcons)) {
14776
15002
  if (showSearch) imports.add("SearchBox");
14777
15003
  imports.add("DefaultButton");
@@ -14823,10 +15049,12 @@ function generateLinkedSubgrid(gridDef, entityName, imports, library = "fluent-v
14823
15049
  toolbarIcons.push(
14824
15050
  `<IconButton iconProps={{ iconName: 'ColumnOptions' }} title="Column chooser" ariaLabel="Column chooser" />`
14825
15051
  );
14826
- if (showExport)
15052
+ if (showExport) {
15053
+ const exportOnClick = _handlerAvailable ? ` onClick={() => { void handleGridCommand('export'); }}` : "";
14827
15054
  toolbarIcons.push(
14828
- `<IconButton iconProps={{ iconName: 'Download' }} title="Export" ariaLabel="Export" />`
15055
+ `<IconButton iconProps={{ iconName: 'Download' }} title="Export" ariaLabel="Export"${exportOnClick} />`
14829
15056
  );
15057
+ }
14830
15058
  if (showRefresh) {
14831
15059
  const refreshOnClick = _handlerAvailable ? ` onClick={() => { void handleGridCommand('refresh'); }}` : "";
14832
15060
  toolbarIcons.push(
@@ -14850,6 +15078,9 @@ function generateLinkedSubgrid(gridDef, entityName, imports, library = "fluent-v
14850
15078
  const childGridDef = gridDef.nestedGridId ? _currentGridCustomizers.find((g) => g.id === gridDef.nestedGridId) : void 0;
14851
15079
  const isGridKitNested = !!childGridDef;
14852
15080
  const visibleCols = [...gridDef.columns].filter((c) => c.isVisible).sort((a, b) => a.order - b.order);
15081
+ const exportColumnsLiteral = enableExport ? buildExportColumnsLiteral(visibleCols) : "";
15082
+ const primaryIdAttr = resolvePrimaryIdAttribute(gridDef);
15083
+ const createColumnsLiteral = enableCreate ? buildCreateFields(visibleCols, primaryIdAttr) : "";
14853
15084
  visibleCols.filter((c) => c.rendererType !== "text");
14854
15085
  const colEntries = isGridKitNested ? "" : generateColumnEntries(visibleCols, library);
14855
15086
  const selectionMode = gridDef.selectionMode === "multiple" ? "SelectionMode.multiple" : gridDef.selectionMode === "single" ? "SelectionMode.single" : "SelectionMode.none";
@@ -14915,19 +15146,27 @@ ${childEntries.join(",\n")},
14915
15146
  entitySetName: gridDef.dataSource.entitySetName,
14916
15147
  primaryIdAttribute: resolvePrimaryIdAttribute(gridDef),
14917
15148
  itemsExpr: "keyedItems",
14918
- selectionExpr: "selectedIndices"
15149
+ selectionExpr: "selectedIndices",
15150
+ exportColumnsLiteral,
15151
+ createColumnsLiteral
14919
15152
  }) : "";
14920
15153
  const ngHasDelete = (gridDef.commandBarItems ?? []).some(
14921
15154
  (ci) => ci.actionType === "delete"
14922
15155
  );
14923
15156
  const ngDeleteDialogJsx = ngHasDelete && ngHandlerBlock !== "" ? buildGridDeleteDialogJsx(gridDef.dataSource?.entityName ?? entityName) : "";
14924
- if (ngDeleteDialogJsx) {
15157
+ const ngCreateDialogJsx = ngHandlerBlock !== "" && createColumnsLiteral ? buildGridCreateDialogJsx(gridDef.dataSource?.entityName ?? entityName) : "";
15158
+ if (ngDeleteDialogJsx || ngCreateDialogJsx) {
14925
15159
  imports.add("Dialog");
14926
15160
  imports.add("DialogType");
14927
15161
  imports.add("DialogFooter");
14928
15162
  imports.add("PrimaryButton");
14929
15163
  imports.add("DefaultButton");
14930
15164
  }
15165
+ if (ngCreateDialogJsx) {
15166
+ imports.add("TextField");
15167
+ imports.add("Dropdown");
15168
+ imports.add("Checkbox");
15169
+ }
14931
15170
  const ngRowCommandsLiteral = _handlerAvailable && (ngHasCommandBar || ngHasContextMenu && useLiveNested) ? buildRowCommandsLiteral(contextMenuItems) : "";
14932
15171
  const ngToolbarWired = (gridDef.commandBarItems ?? []).some(
14933
15172
  (ci) => (ci.actionType ?? "custom") !== "custom"
@@ -14941,9 +15180,18 @@ ${childEntries.join(",\n")},
14941
15180
  const ngDataverseHooks = [
14942
15181
  ...useLiveNested ? ["useDataverseQuery"] : [],
14943
15182
  ...useLiveNested && ngNeedsUpdate || ngHasHandler ? ["useUpdateRecord"] : [],
14944
- ...useLiveNested && ngNeedsDelete || ngHasHandler ? ["useDeleteRecord"] : []
15183
+ ...useLiveNested && ngNeedsDelete || ngHasHandler ? ["useDeleteRecord"] : [],
15184
+ // Create mutation only when the create dialog is wired (the `new` case's
15185
+ // non-Xrm fallback) — gated like the export util import to stay byte-stable
15186
+ // for non-create handler grids.
15187
+ ...ngHasHandler && createColumnsLiteral ? ["useCreateRecord"] : []
15188
+ ];
15189
+ const ngCommandBarImports = [
15190
+ ...useLiveNested && ngNeedsDelete || ngHasHandler ? [`import { useQueryClient } from '@tanstack/react-query';`] : [],
15191
+ // Export case (shared dispatcher → both nested + focused-view) calls
15192
+ // exportToFile/generateDefaultFilename when export is wired.
15193
+ ...ngHasHandler && enableExport ? [`import { exportToFile, generateDefaultFilename } from '../lib/grid-kit/utils';`] : []
14945
15194
  ];
14946
- const ngCommandBarImports = useLiveNested && ngNeedsDelete || ngHasHandler ? [`import { useQueryClient } from '@tanstack/react-query';`] : [];
14947
15195
  const ngParentSelectionMode = ngHasCommandBar ? gridDef.selectionMode === "single" ? "single" : "multiple" : gridDef.selectionMode ?? "none";
14948
15196
  const ngChildSaveBack = Boolean(
14949
15197
  useLiveNested && live && (childGridDef.isEditable ?? false)
@@ -14992,6 +15240,7 @@ ${childEntries.join(",\n")},
14992
15240
  commandBarBlock: gridCommandBarBlock,
14993
15241
  handlerBlock: ngHandlerBlock,
14994
15242
  deleteDialogJsx: ngDeleteDialogJsx,
15243
+ createDialogJsx: ngCreateDialogJsx,
14995
15244
  hasSelection: ngHasCommandBar,
14996
15245
  hasHandler: ngHandlerBlock !== "",
14997
15246
  dataverseHooks: ngDataverseHooks,
@@ -15047,6 +15296,7 @@ ${childEntries.join(",\n")},
15047
15296
  commandBarBlock: gridCommandBarBlock,
15048
15297
  handlerBlock: ngHandlerBlock,
15049
15298
  deleteDialogJsx: ngDeleteDialogJsx,
15299
+ createDialogJsx: ngCreateDialogJsx,
15050
15300
  hasSelection: ngHasCommandBar,
15051
15301
  dataverseHooks: ngDataverseHooks,
15052
15302
  commandBarImports: ngCommandBarImports,
@@ -15127,11 +15377,14 @@ ${childEntries.join(",\n")},
15127
15377
  entitySetName: gridDef.dataSource.entitySetName,
15128
15378
  primaryIdAttribute: resolvePrimaryIdAttribute(gridDef),
15129
15379
  itemsExpr: "rows",
15130
- selectionExpr: "selectedIndices"
15380
+ selectionExpr: "selectedIndices",
15381
+ exportColumnsLiteral,
15382
+ createColumnsLiteral
15131
15383
  }) : "";
15132
15384
  const cardSelectedIndicesState = cardEmitHandler ? "\n const [selectedIndices, setSelectedIndices] = React.useState<Set<number>>(new Set());" : "";
15133
15385
  const cardRowCommandsLiteral = cardEmitRowCommands ? buildRowCommandsLiteral(contextMenuItems) : "";
15134
15386
  const cardDeleteDialogJsx = cardEmitRowCommands && flatHasDelete || cardEmitToolbar && flatBarHasDelete ? buildGridDeleteDialogJsx(gridDef.dataSource?.entityName ?? entityName) : "";
15387
+ const cardCreateDialogJsx = cardEmitHandler && createColumnsLiteral ? buildGridCreateDialogJsx(gridDef.dataSource?.entityName ?? entityName) : "";
15135
15388
  const cardCommandBarBlock = cardEmitToolbar ? gridCommandBarBlock : "";
15136
15389
  const cardSelectionModeAttr = cardEmitToolbar ? flatSelectionModeAttr : "";
15137
15390
  const cardOnSelectionChangedAttr = cardEmitToolbar ? flatOnSelectionChangedAttr : "";
@@ -15150,20 +15403,26 @@ ${childEntries.join(",\n")},
15150
15403
  handlerBlock: cardHandlerBlock,
15151
15404
  rowCommandsLiteral: cardRowCommandsLiteral,
15152
15405
  deleteDialogJsx: cardDeleteDialogJsx,
15406
+ createDialogJsx: cardCreateDialogJsx,
15153
15407
  commandBarBlock: cardCommandBarBlock,
15154
15408
  selectionModeAttr: cardSelectionModeAttr,
15155
15409
  onSelectionChangedAttr: cardOnSelectionChangedAttr
15156
15410
  })
15157
15411
  ];
15158
15412
  _usesGridKit = true;
15159
- const cardDataverseImport = cardEmitHandler ? `import { useDataverseQuery, useUpdateRecord, useDeleteRecord } from '../lib/dataverse';` : `import { useDataverseQuery } from '../lib/dataverse';`;
15160
- if (cardDeleteDialogJsx) {
15413
+ const cardDataverseImport = cardEmitHandler ? `import { useDataverseQuery, useUpdateRecord, useDeleteRecord${createColumnsLiteral ? ", useCreateRecord" : ""} } from '../lib/dataverse';` : `import { useDataverseQuery } from '../lib/dataverse';`;
15414
+ if (cardDeleteDialogJsx || cardCreateDialogJsx) {
15161
15415
  imports.add("Dialog");
15162
15416
  imports.add("DialogType");
15163
15417
  imports.add("DialogFooter");
15164
15418
  imports.add("PrimaryButton");
15165
15419
  imports.add("DefaultButton");
15166
15420
  }
15421
+ if (cardCreateDialogJsx) {
15422
+ imports.add("TextField");
15423
+ imports.add("Dropdown");
15424
+ imports.add("Checkbox");
15425
+ }
15167
15426
  return {
15168
15427
  imports,
15169
15428
  v9Imports,
@@ -15171,7 +15430,10 @@ ${childEntries.join(",\n")},
15171
15430
  extraImports: cardIsLive ? cardEmitHandler ? [
15172
15431
  cardDataverseImport,
15173
15432
  `import { useQueryClient } from '@tanstack/react-query';`,
15174
- `import { CardGrid, createCellRegistry, type ColumnDef } from '../lib/grid-kit';`
15433
+ `import { CardGrid, createCellRegistry, type ColumnDef } from '../lib/grid-kit';`,
15434
+ // Export case calls exportToFile/generateDefaultFilename (cardEmitHandler
15435
+ // here; the export case is only real when enableExport).
15436
+ ...enableExport ? [`import { exportToFile, generateDefaultFilename } from '../lib/grid-kit/utils';`] : []
15175
15437
  ] : [
15176
15438
  `import { useDataverseQuery } from '../lib/dataverse';`,
15177
15439
  `import { CardGrid, createCellRegistry, type ColumnDef } from '../lib/grid-kit';`
@@ -15209,11 +15471,14 @@ ${childEntries.join(",\n")},
15209
15471
  entitySetName: gridDef.dataSource.entitySetName,
15210
15472
  primaryIdAttribute: resolvePrimaryIdAttribute(gridDef),
15211
15473
  itemsExpr: "rows",
15212
- selectionExpr: "selectedIndices"
15474
+ selectionExpr: "selectedIndices",
15475
+ exportColumnsLiteral,
15476
+ createColumnsLiteral
15213
15477
  }) : "";
15214
15478
  const roSelectedIndicesState = roEmitHandler ? "\n const [selectedIndices, setSelectedIndices] = React.useState<Set<number>>(new Set());" : "";
15215
15479
  const roRowCommandsLiteral = roEmitRowCommands ? buildRowCommandsLiteral(contextMenuItems) : "";
15216
15480
  const roDeleteDialogJsx = roEmitRowCommands && flatHasDelete ? buildGridDeleteDialogJsx(gridDef.dataSource?.entityName ?? entityName) : "";
15481
+ const roCreateDialogJsx = roEmitHandler && createColumnsLiteral ? buildGridCreateDialogJsx(gridDef.dataSource?.entityName ?? entityName) : "";
15217
15482
  const roCommandBarBlock = roEmitToolbar ? gridCommandBarBlock : "";
15218
15483
  const helperComponents = [
15219
15484
  buildLiveReadOnlySubgridBody({
@@ -15232,18 +15497,24 @@ ${childEntries.join(",\n")},
15232
15497
  handlerBlock: roHandlerBlock,
15233
15498
  rowCommandsLiteral: roRowCommandsLiteral,
15234
15499
  deleteDialogJsx: roDeleteDialogJsx,
15500
+ createDialogJsx: roCreateDialogJsx,
15235
15501
  commandBarBlock: roCommandBarBlock
15236
15502
  })
15237
15503
  ];
15238
15504
  if (!isV9) _usesGridKit = true;
15239
- const roDataverseImport = roEmitHandler ? `import { useDataverseQuery, useUpdateRecord, useDeleteRecord } from '../lib/dataverse';` : `import { useDataverseQuery } from '../lib/dataverse';`;
15240
- if (roDeleteDialogJsx) {
15505
+ const roDataverseImport = roEmitHandler ? `import { useDataverseQuery, useUpdateRecord, useDeleteRecord${createColumnsLiteral ? ", useCreateRecord" : ""} } from '../lib/dataverse';` : `import { useDataverseQuery } from '../lib/dataverse';`;
15506
+ if (roDeleteDialogJsx || roCreateDialogJsx) {
15241
15507
  imports.add("Dialog");
15242
15508
  imports.add("DialogType");
15243
15509
  imports.add("DialogFooter");
15244
15510
  imports.add("PrimaryButton");
15245
15511
  imports.add("DefaultButton");
15246
15512
  }
15513
+ if (roCreateDialogJsx) {
15514
+ imports.add("TextField");
15515
+ imports.add("Dropdown");
15516
+ imports.add("Checkbox");
15517
+ }
15247
15518
  return {
15248
15519
  imports,
15249
15520
  v9Imports,
@@ -15251,7 +15522,8 @@ ${childEntries.join(",\n")},
15251
15522
  extraImports: isV9 ? [`import { useDataverseQuery } from '../lib/dataverse';`] : roEmitHandler ? [
15252
15523
  roDataverseImport,
15253
15524
  `import { useQueryClient } from '@tanstack/react-query';`,
15254
- `import { ReadOnlyGrid, createCellRegistry, type ColumnDef } from '../lib/grid-kit';`
15525
+ `import { ReadOnlyGrid, createCellRegistry, type ColumnDef } from '../lib/grid-kit';`,
15526
+ ...enableExport ? [`import { exportToFile, generateDefaultFilename } from '../lib/grid-kit/utils';`] : []
15255
15527
  ] : [
15256
15528
  `import { useDataverseQuery } from '../lib/dataverse';`,
15257
15529
  `import { ReadOnlyGrid, createCellRegistry, type ColumnDef } from '../lib/grid-kit';`
@@ -15311,11 +15583,14 @@ const ${liveWrapperName}: React.FC = () => {
15311
15583
  entitySetName: gridDef.dataSource.entitySetName,
15312
15584
  primaryIdAttribute: resolvePrimaryIdAttribute(gridDef),
15313
15585
  itemsExpr: "keyedItems",
15314
- selectionExpr: "selectedIndices"
15586
+ selectionExpr: "selectedIndices",
15587
+ exportColumnsLiteral,
15588
+ createColumnsLiteral
15315
15589
  }) : "";
15316
15590
  const edSelectedIndicesState = edEmitHandler ? "\n const [selectedIndices, setSelectedIndices] = React.useState<Set<number>>(new Set());" : "";
15317
15591
  const edRowCommandsLiteral = edEmitRowCommands ? buildRowCommandsLiteral(contextMenuItems) : "";
15318
15592
  const edDeleteDialogJsx = edEmitRowCommands && flatHasDelete || edEmitToolbar && flatBarHasDelete ? buildGridDeleteDialogJsx(gridDef.dataSource?.entityName ?? entityName) : "";
15593
+ const edCreateDialogJsx = edEmitHandler && createColumnsLiteral ? buildGridCreateDialogJsx(gridDef.dataSource?.entityName ?? entityName) : "";
15319
15594
  const edCommandBarBlock = edEmitToolbar ? gridCommandBarBlock : "";
15320
15595
  const edSelectionModeAttr = edEmitToolbar ? flatSelectionModeAttr : "";
15321
15596
  const edOnSelectionChangedAttr = edEmitToolbar ? flatOnSelectionChangedAttr : "";
@@ -15332,6 +15607,7 @@ const ${liveWrapperName}: React.FC = () => {
15332
15607
  handlerBlock: edHandlerBlock,
15333
15608
  rowCommandsLiteral: edRowCommandsLiteral,
15334
15609
  deleteDialogJsx: edDeleteDialogJsx,
15610
+ createDialogJsx: edCreateDialogJsx,
15335
15611
  commandBarBlock: edCommandBarBlock,
15336
15612
  selectionModeAttr: edSelectionModeAttr,
15337
15613
  onSelectionChangedAttr: edOnSelectionChangedAttr
@@ -15343,18 +15619,24 @@ const ${liveWrapperName}: React.FC = () => {
15343
15619
  const jsx3 = editableUsesLiveData && liveWrapperName ? `{/* Subgrid: ${safeEntityName} \u2014 ${safeGridName} (editable, live) */}${todoComment}
15344
15620
  <${liveWrapperName} />` : `{/* Subgrid: ${safeEntityName} \u2014 ${safeGridName} (editable) */}${todoComment}
15345
15621
  <${componentName} items={${itemsLiteral}} />`;
15346
- const edDataverseImport = edEmitHandler ? `import { useDataverseQuery, useUpdateRecord, useDeleteRecord } from '../lib/dataverse';` : `import { useDataverseQuery, useUpdateRecord } from '../lib/dataverse';`;
15347
- if (edDeleteDialogJsx) {
15622
+ const edDataverseImport = edEmitHandler ? `import { useDataverseQuery, useUpdateRecord, useDeleteRecord${createColumnsLiteral ? ", useCreateRecord" : ""} } from '../lib/dataverse';` : `import { useDataverseQuery, useUpdateRecord } from '../lib/dataverse';`;
15623
+ if (edDeleteDialogJsx || edCreateDialogJsx) {
15348
15624
  imports.add("Dialog");
15349
15625
  imports.add("DialogType");
15350
15626
  imports.add("DialogFooter");
15351
15627
  imports.add("PrimaryButton");
15352
15628
  imports.add("DefaultButton");
15353
15629
  }
15630
+ if (edCreateDialogJsx) {
15631
+ imports.add("TextField");
15632
+ imports.add("Dropdown");
15633
+ imports.add("Checkbox");
15634
+ }
15354
15635
  const extraImports2 = editableUsesLiveData ? edEmitHandler ? [
15355
15636
  edDataverseImport,
15356
15637
  `import { useQueryClient } from '@tanstack/react-query';`,
15357
- `import { DataGrid, createCellRegistry, type ColumnDef } from '../lib/grid-kit';`
15638
+ `import { DataGrid, createCellRegistry, type ColumnDef } from '../lib/grid-kit';`,
15639
+ ...enableExport ? [`import { exportToFile, generateDefaultFilename } from '../lib/grid-kit/utils';`] : []
15358
15640
  ] : [
15359
15641
  `import { useDataverseQuery, useUpdateRecord } from '../lib/dataverse';`,
15360
15642
  `import { DataGrid, createCellRegistry, type ColumnDef } from '../lib/grid-kit';`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dataverse-kit/export-engine",
3
- "version": "1.4.0",
3
+ "version": "1.5.0",
4
4
  "type": "module",
5
5
  "main": "./dist/index.cjs",
6
6
  "module": "./dist/index.mjs",