@khester/create-dynamics-app 3.0.0 → 3.2.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 (93) hide show
  1. package/README.md +2 -2
  2. package/dist/artifacts/registry.d.ts.map +1 -1
  3. package/dist/artifacts/registry.js +76 -20
  4. package/dist/artifacts/registry.js.map +1 -1
  5. package/dist/index.js +8 -0
  6. package/dist/index.js.map +1 -1
  7. package/dist/templatePlan.d.ts.map +1 -1
  8. package/dist/templatePlan.js +20 -3
  9. package/dist/templatePlan.js.map +1 -1
  10. package/dist/utils/copyTemplate.d.ts.map +1 -1
  11. package/dist/utils/copyTemplate.js +4 -0
  12. package/dist/utils/copyTemplate.js.map +1 -1
  13. package/package.json +1 -1
  14. package/templates/_shared/dataverse-seam/XrmApiService.ts +26 -5
  15. package/templates/grid-customizer/README.md +105 -15
  16. package/templates/grid-customizer/dev/harness/MockGrid.tsx +165 -0
  17. package/templates/grid-customizer/dev/harness/dataverse.ts +168 -0
  18. package/templates/grid-customizer/dev/index.html +20 -0
  19. package/templates/grid-customizer/dev/main.tsx +260 -0
  20. package/templates/grid-customizer/dev/vite-env.d.ts +1 -0
  21. package/templates/grid-customizer/env.local.example +24 -0
  22. package/templates/grid-customizer/eslintrc.json +11 -0
  23. package/templates/grid-customizer/featureconfig.json +3 -0
  24. package/templates/grid-customizer/gitignore +3 -0
  25. package/templates/grid-customizer/package.json +30 -16
  26. package/templates/grid-customizer/pcfconfig.json +3 -0
  27. package/templates/grid-customizer/tsconfig.dev.json +16 -0
  28. package/templates/grid-customizer/tsconfig.json +17 -10
  29. package/templates/grid-customizer/vite.config.ts +64 -0
  30. package/templates/grid-customizer/{{componentName}}/ControlManifest.Input.xml +63 -0
  31. package/templates/grid-customizer/{{componentName}}/customizers/CellEditorOverrides.tsx +38 -0
  32. package/templates/grid-customizer/{{componentName}}/customizers/CellRendererOverrides.tsx +109 -0
  33. package/templates/grid-customizer/{{componentName}}/customizers/cells.tsx +137 -0
  34. package/templates/grid-customizer/{{componentName}}/customizers/config.ts +84 -0
  35. package/templates/grid-customizer/{{componentName}}/index.ts +81 -0
  36. package/templates/grid-customizer/{{componentName}}/strings/{{componentName}}.1033.resx +56 -0
  37. package/templates/grid-customizer/{{componentName}}/types.ts +168 -0
  38. package/templates/host-swap/README.md +57 -2
  39. package/templates/host-swap/_variants/host.custom-page/src/App.tsx +9 -5
  40. package/templates/host-swap/_variants/host.pcf/ControlManifest.Input.xml +26 -2
  41. package/templates/host-swap/_variants/host.pcf/gitignore +7 -0
  42. package/templates/host-swap/_variants/host.pcf/index.ts +21 -3
  43. package/templates/host-swap/_variants/host.pcf/package.json +20 -4
  44. package/templates/host-swap/_variants/host.pcf/{{componentName}}.pcfproj +67 -0
  45. package/templates/host-swap/_variants/host.web-resource/src/App.tsx +5 -1
  46. package/templates/host-swap/src/Component.tsx +54 -7
  47. package/templates/host-swap/src/useHostWidth.ts +53 -0
  48. package/templates/pcf-dataset/dev/harness/dataverse.ts +82 -0
  49. package/templates/pcf-dataset/dev/index.html +20 -0
  50. package/templates/pcf-dataset/dev/main.tsx +194 -0
  51. package/templates/pcf-dataset/dev/vite-env.d.ts +1 -0
  52. package/templates/pcf-dataset/env.local.example +24 -0
  53. package/templates/pcf-dataset/eslintrc.json +11 -0
  54. package/templates/pcf-dataset/gitignore +4 -0
  55. package/templates/pcf-dataset/package.json +18 -8
  56. package/templates/pcf-dataset/pcfconfig.json +3 -0
  57. package/templates/pcf-dataset/tsconfig.dev.json +15 -0
  58. package/templates/pcf-dataset/tsconfig.json +11 -2
  59. package/templates/pcf-dataset/vite.config.ts +63 -0
  60. package/templates/pcf-dataset/{{componentName}}/ControlManifest.Input.xml +52 -0
  61. package/templates/pcf-dataset/{{componentName}}/{{componentName}}App.tsx +64 -0
  62. package/templates/pcf-dataset/{{{componentName}}Component.tsx → {{componentName}}/{{componentName}}Component.tsx} +9 -46
  63. package/templates/pcf-field/dev/harness/dataverse.ts +71 -0
  64. package/templates/pcf-field/dev/index.html +20 -0
  65. package/templates/pcf-field/dev/main.tsx +212 -0
  66. package/templates/pcf-field/dev/vite-env.d.ts +1 -0
  67. package/templates/pcf-field/env.local.example +24 -0
  68. package/templates/pcf-field/eslintrc.json +11 -0
  69. package/templates/pcf-field/gitignore +4 -0
  70. package/templates/pcf-field/package.json +11 -2
  71. package/templates/pcf-field/pcfconfig.json +3 -0
  72. package/templates/pcf-field/tsconfig.dev.json +15 -0
  73. package/templates/pcf-field/tsconfig.json +20 -4
  74. package/templates/pcf-field/vite.config.ts +60 -0
  75. package/templates/pcf-field/{{componentName}}/ControlManifest.Input.xml +42 -0
  76. package/templates/pcf-field/{{{componentName}}Component.tsx → {{componentName}}/{{componentName}}Component.tsx} +6 -1
  77. package/templates/power-pages-island/README.md +12 -0
  78. package/templates/power-pages-island/src/App.tsx +13 -1
  79. package/templates/power-pages-island/src/Component.tsx +54 -7
  80. package/templates/power-pages-island/src/useHostWidth.ts +53 -0
  81. package/templates/react-custom-page/tools/deploy/deploy-webresource.cjs +8 -2
  82. package/templates/grid-customizer/src/GridCustomizer.ts +0 -28
  83. package/templates/grid-customizer/src/cell-renderers.tsx +0 -35
  84. package/templates/grid-customizer/src/index.ts +0 -4
  85. package/templates/grid-customizer/src/types/grid-types.ts +0 -30
  86. package/templates/grid-customizer/src/utils/color-utils.ts +0 -24
  87. package/templates/grid-customizer/webpack.config.js +0 -17
  88. package/templates/pcf-dataset/ControlManifest.Input.xml +0 -16
  89. package/templates/pcf-field/ControlManifest.Input.xml +0 -17
  90. /package/templates/pcf-dataset/{index.ts → {{componentName}}/index.ts} +0 -0
  91. /package/templates/pcf-dataset/{strings → {{componentName}}/strings}/{{componentName}}.1033.resx +0 -0
  92. /package/templates/pcf-field/{index.ts → {{componentName}}/index.ts} +0 -0
  93. /package/templates/pcf-field/{strings → {{componentName}}/strings}/{{componentName}}.1033.resx +0 -0
@@ -0,0 +1,53 @@
1
+ import * as React from "react";
2
+
3
+ /**
4
+ * Width available to the component, in px, kept live as the host resizes.
5
+ *
6
+ * Two modes, because the browser hosts are not all shaped the same:
7
+ *
8
+ * - **Pass a ref** when the component is mounted into an element on a larger page
9
+ * (a Power Pages island). The viewport is then the whole portal page, which may be
10
+ * far wider than the slot the island occupies, so it must measure its own box.
11
+ * - **Pass nothing** when the component owns a full-page iframe (web resource /
12
+ * Vite host). There the viewport *is* the control's width.
13
+ *
14
+ * The PCF host uses neither — it gets a properly scoped measurement from
15
+ * `context.mode.allocatedWidth` after opting in with `trackContainerResize(true)`.
16
+ *
17
+ * ★ Why measure at all rather than ask the platform what device this is: verified
18
+ * live 2026-07-25 on a physical iPhone in the native Power Apps app, a model-driven
19
+ * custom page reports `client: "Web"` and `formFactor: 1` (Desktop). Form-factor
20
+ * branching therefore hands every phone the desktop layout — and passes every test
21
+ * that is not run on a real device.
22
+ */
23
+ export function useHostWidth(ref?: React.RefObject<HTMLElement | null>): number {
24
+ const [width, setWidth] = React.useState<number>(() =>
25
+ typeof window === "undefined" ? 0 : window.innerWidth,
26
+ );
27
+
28
+ React.useEffect(() => {
29
+ const el = ref?.current;
30
+
31
+ if (el && typeof ResizeObserver !== "undefined") {
32
+ const ro = new ResizeObserver((entries) => {
33
+ for (const entry of entries) setWidth(entry.contentRect.width);
34
+ });
35
+ ro.observe(el);
36
+ setWidth(el.getBoundingClientRect().width);
37
+ return () => ro.disconnect();
38
+ }
39
+
40
+ // Viewport fallback: no ref, or an environment without ResizeObserver.
41
+ const onResize = () => setWidth(window.innerWidth);
42
+ window.addEventListener("resize", onResize);
43
+ // iOS Safari does not reliably fire `resize` on rotation, so listen for both.
44
+ window.addEventListener("orientationchange", onResize);
45
+ onResize();
46
+ return () => {
47
+ window.removeEventListener("resize", onResize);
48
+ window.removeEventListener("orientationchange", onResize);
49
+ };
50
+ }, [ref]);
51
+
52
+ return width;
53
+ }
@@ -107,8 +107,14 @@ async function main() {
107
107
  await req("POST", "PublishXml", { ParameterXml: paramXml });
108
108
  console.log("✓ Published.");
109
109
 
110
- console.log(`\nOpen it: ${url}/WebResources/${name}`);
111
- console.log("Or host it on a model-driven custom page / open via Xrm.Navigation.navigateTo.");
110
+ // Surfacing guidance. NOTE: this app reads window.parent.Xrm / Xrm.Page — a model-driven FORM
111
+ // context. It therefore needs a host that provides one. It CANNOT run in a Power Apps custom page
112
+ // (that is a canvasapp; Xrm.Page does not exist there), and opening the raw /WebResources/ URL
113
+ // standalone gives it no parent Xrm either, so ServiceFactory throws.
114
+ console.log(`\nSurface it via a sitemap SubArea: $webresource:${name}`);
115
+ console.log("Or embed it on a model-driven form, or open it with Xrm.Navigation.navigateTo.");
116
+ console.log(`\nDirect URL (dev only — no parent Xrm, so production mode will fail):`);
117
+ console.log(` ${url}/WebResources/${name}`);
112
118
  }
113
119
 
114
120
  main().catch((e) => {
@@ -1,28 +0,0 @@
1
- import type { IGridCustomizer, CellRendererOverrides } from './types/grid-types';
2
- import {
3
- StatusBadgeRenderer,
4
- CurrencyRenderer,
5
- DateRenderer,
6
- } from './cell-renderers';
7
-
8
- /**
9
- * {{componentName}} — editable-grid customizer. Map a column data type (or a
10
- * specific column logical name) to a cell renderer. Register this customizer on
11
- * the grid control's customizer property.
12
- */
13
- export class {{componentName}} implements IGridCustomizer {
14
- public cellRendererOverrides: CellRendererOverrides = {
15
- // By data type
16
- OptionSet: StatusBadgeRenderer,
17
- Currency: CurrencyRenderer,
18
- // By column logical name
19
- statuscode: StatusBadgeRenderer,
20
- statecode: StatusBadgeRenderer,
21
- createdon: DateRenderer,
22
- modifiedon: DateRenderer,
23
- };
24
-
25
- public cellEditorOverrides?: CellRendererOverrides = {
26
- // Add custom inline editors here, keyed the same way as renderers.
27
- };
28
- }
@@ -1,35 +0,0 @@
1
- import * as React from 'react';
2
- import { Badge, Text } from '@fluentui/react-components';
3
- import type { CellRenderer } from './types/grid-types';
4
- import { getStatusColor } from './utils/color-utils';
5
-
6
- /** Render an option-set / status value as a colored badge. */
7
- export const StatusBadgeRenderer: CellRenderer = ({ value, formattedValue }) => {
8
- const text = formattedValue ?? String(value ?? '');
9
- return (
10
- <Badge appearance="filled" style={{ backgroundColor: getStatusColor(text) }}>
11
- {text}
12
- </Badge>
13
- );
14
- };
15
-
16
- /** Render a number as currency. */
17
- export const CurrencyRenderer: CellRenderer = ({ value }) => {
18
- const n = typeof value === 'number' ? value : Number(value);
19
- const text = Number.isNaN(n)
20
- ? ''
21
- : new Intl.NumberFormat(undefined, { style: 'currency', currency: 'USD' }).format(n);
22
- return <Text>{text}</Text>;
23
- };
24
-
25
- /** Render a 0–100 number as a percentage. */
26
- export const ProgressRenderer: CellRenderer = ({ value }) => {
27
- const n = Math.max(0, Math.min(100, Number(value) || 0));
28
- return <Text>{n}%</Text>;
29
- };
30
-
31
- /** Render a date value as a localized date string. */
32
- export const DateRenderer: CellRenderer = ({ value }) => {
33
- const d = value instanceof Date ? value : new Date(String(value));
34
- return <Text>{Number.isNaN(d.getTime()) ? '' : d.toLocaleDateString()}</Text>;
35
- };
@@ -1,4 +0,0 @@
1
- export { {{componentName}} } from './GridCustomizer';
2
- export * from './cell-renderers';
3
- export * from './types/grid-types';
4
- export * from './utils/color-utils';
@@ -1,30 +0,0 @@
1
- import type * as React from 'react';
2
-
3
- export interface GridColumn {
4
- name: string;
5
- displayName: string;
6
- dataType: string;
7
- }
8
-
9
- export interface CellRendererProps {
10
- value: unknown;
11
- formattedValue?: string;
12
- column: GridColumn;
13
- rowData: Record<string, unknown>;
14
- entityId: string;
15
- entityName: string;
16
- isEditing: boolean;
17
- isDisabled: boolean;
18
- onChange?: (value: unknown) => void;
19
- }
20
-
21
- export type CellRenderer = React.FC<CellRendererProps>;
22
-
23
- export type CellRendererOverrides = {
24
- [key: string]: CellRenderer;
25
- };
26
-
27
- export interface IGridCustomizer {
28
- cellRendererOverrides: CellRendererOverrides;
29
- cellEditorOverrides?: CellRendererOverrides;
30
- }
@@ -1,24 +0,0 @@
1
- export function stringToColor(str: string): string {
2
- let hash = 0;
3
- for (let i = 0; i < str.length; i++) {
4
- hash = str.charCodeAt(i) + ((hash << 5) - hash);
5
- }
6
- const hue = hash % 360;
7
- return `hsl(${hue}, 65%, 45%)`;
8
- }
9
-
10
- export const STATUS_COLORS: Record<string, string> = {
11
- active: '#107c10',
12
- inactive: '#a19f9d',
13
- draft: '#0078d4',
14
- pending: '#ffaa44',
15
- approved: '#107c10',
16
- rejected: '#d13438',
17
- completed: '#107c10',
18
- cancelled: '#a19f9d',
19
- };
20
-
21
- export function getStatusColor(status: string): string {
22
- const normalized = status.toLowerCase().replace(/\s+/g, '');
23
- return STATUS_COLORS[normalized] || stringToColor(status);
24
- }
@@ -1,17 +0,0 @@
1
- const path = require('path');
2
-
3
- module.exports = {
4
- entry: './src/index.ts',
5
- output: {
6
- path: path.resolve(__dirname, 'dist'),
7
- filename: '{{projectName}}.js',
8
- library: { type: 'umd', name: '{{componentName}}' },
9
- clean: true,
10
- },
11
- resolve: { extensions: ['.ts', '.tsx', '.js'] },
12
- module: {
13
- rules: [{ test: /\.tsx?$/, use: 'ts-loader', exclude: /node_modules/ }],
14
- },
15
- // Provided by the grid host at runtime.
16
- externals: { react: 'React', 'react-dom': 'ReactDOM' },
17
- };
@@ -1,16 +0,0 @@
1
- <?xml version="1.0" encoding="utf-8" ?>
2
- <manifest>
3
- <control namespace="{{namespace}}" constructor="{{componentName}}" version="1.0.0" display-name-key="{{namespace}}_{{componentName}}" description-key="{{namespace}}_{{componentName}}_Desc" control-type="virtual">
4
- <data-set name="dataset" display-name-key="Dataset_Display_Key">
5
- </data-set>
6
- <resources>
7
- <code path="index.ts" order="1"/>
8
- <platform-library name="React" version="18.2.0" />
9
- <platform-library name="Fluent" version="8.115.6" />
10
- <resx path="strings/{{componentName}}.1033.resx" version="1.0.0" />
11
- </resources>
12
- <feature-usage>
13
- <uses-feature name="Utility" required="true" />
14
- </feature-usage>
15
- </control>
16
- </manifest>
@@ -1,17 +0,0 @@
1
- <?xml version="1.0" encoding="utf-8" ?>
2
- <manifest>
3
- <control namespace="{{namespace}}" constructor="{{componentName}}" version="1.0.0" display-name-key="{{namespace}}_{{componentName}}" description-key="{{namespace}}_{{componentName}}_Desc" control-type="standard">
4
- <property name="value" display-name-key="Value" description-key="Value_Desc" of-type="{{propertyType}}" usage="bound" required="true" />
5
- <property name="placeholder" display-name-key="Placeholder" description-key="Placeholder_Desc" of-type="SingleLine.Text" usage="input" required="false" />
6
- <property name="disabled" display-name-key="Disabled" description-key="Disabled_Desc" of-type="TwoOptions" usage="input" required="false" default-value="false" />
7
- <resources>
8
- <code path="index.ts" order="1"/>
9
- <platform-library name="React" version="16.8.6" />
10
- <platform-library name="Fluent" version="8.29.0" />
11
- <resx path="strings/{{componentName}}.1033.resx" version="1.0.0" />
12
- </resources>
13
- <feature-usage>
14
- <uses-feature name="Utility" required="true" />
15
- </feature-usage>
16
- </control>
17
- </manifest>