@svgrid/enterprise 2.3.2 → 2.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.
- package/dist/cdn/svgrid-enterprise.svelte-external.js +5917 -5545
- package/dist/designer/assets/index-oYok52zd.css +1 -0
- package/dist/designer/assets/{index-DVrv9dH6.js → index-qL7ir9C7.js} +89 -89
- package/dist/designer/assets/src-96d31O63.js +2873 -0
- package/dist/designer/assets/src-D-O2F805.css +1 -0
- package/dist/designer/index.html +6 -6
- package/dist/node/studio.js +681 -84
- package/package.json +3 -3
- package/src/SvGridEditPanel.dom.test.ts +174 -0
- package/src/SvGridEditPanel.svelte +209 -46
- package/src/edit-panel.test.ts +145 -36
- package/src/edit-panel.ts +167 -16
- package/src/expressions/evaluate.ts +1 -1
- package/src/expressions/parse.ts +3 -3
- package/src/index.ts +8 -0
- package/src/license-core.ts +77 -5
- package/src/license-expiry.test.ts +91 -0
- package/src/license.ts +31 -2
- package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/_svelte_metadata.json +1 -0
- package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/_metadata.json +178 -0
- package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/attachments-ZrPsKxob.js +3875 -0
- package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/attachments-ZrPsKxob.js.map +1 -0
- package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/constants-ACqvWGE2.js +21 -0
- package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/constants-ACqvWGE2.js.map +1 -0
- package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/dev-D3w5jt92.js +1938 -0
- package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/dev-D3w5jt92.js.map +1 -0
- package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/events-CC2V3H0y.js +205 -0
- package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/events-CC2V3H0y.js.map +1 -0
- package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/flags-BXP37tAa.js +18 -0
- package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/flags-BXP37tAa.js.map +1 -0
- package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/hydration-DwubnU5b.js +264 -0
- package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/hydration-DwubnU5b.js.map +1 -0
- package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/legacy-client-hD7f6PUz.js +697 -0
- package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/legacy-client-hD7f6PUz.js.map +1 -0
- package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/package.json +3 -0
- package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte.js +134 -0
- package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte.js.map +1 -0
- package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_animate.js +55 -0
- package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_animate.js.map +1 -0
- package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_attachments.js +2 -0
- package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_compiler.js +46843 -0
- package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_compiler.js.map +1 -0
- package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_easing.js +237 -0
- package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_easing.js.map +1 -0
- package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_events.js +2 -0
- package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_internal.js +4 -0
- package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_internal.js.map +1 -0
- package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_internal_client.js +7 -0
- package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_internal_disclose-version.js +4 -0
- package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_internal_disclose-version.js.map +1 -0
- package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_internal_flags_async.js +5 -0
- package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_internal_flags_async.js.map +1 -0
- package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_internal_flags_legacy.js +5 -0
- package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_internal_flags_legacy.js.map +1 -0
- package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_internal_flags_tracing.js +5 -0
- package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_internal_flags_tracing.js.map +1 -0
- package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_internal_server.js +6 -0
- package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_legacy.js +83 -0
- package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_legacy.js.map +1 -0
- package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_motion.js +559 -0
- package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_motion.js.map +1 -0
- package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_reactivity.js +24 -0
- package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_reactivity.js.map +1 -0
- package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_reactivity_window.js +122 -0
- package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_reactivity_window.js.map +1 -0
- package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_server.js +2 -0
- package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_store.js +89 -0
- package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_store.js.map +1 -0
- package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_transition.js +241 -0
- package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_transition.js.map +1 -0
- package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/utils-DBx1SCN6.js +4938 -0
- package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/utils-DBx1SCN6.js.map +1 -0
- package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/utils-gjfpSTFS.js +495 -0
- package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/utils-gjfpSTFS.js.map +1 -0
- package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/validate-DKoOKg8S.js +190 -0
- package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/validate-DKoOKg8S.js.map +1 -0
- package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/version-C0MiR7eR.js +8 -0
- package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/version-C0MiR7eR.js.map +1 -0
- package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/warnings-B6Y7P0Q-.js +138 -0
- package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/warnings-B6Y7P0Q-.js.map +1 -0
- package/src/schema.ts +63 -0
- package/src/studio/emit-project.test.ts +191 -0
- package/src/studio/emit-project.ts +237 -27
- package/src/studio/emit-schema.ts +4 -1
- package/src/studio/index.ts +2 -0
- package/src/studio/init-flow.test.ts +150 -0
- package/src/studio/init-flow.ts +169 -22
- package/src/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/_svelte_metadata.json +1 -0
- package/src/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/_metadata.json +178 -0
- package/src/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/attachments-BOv1rBKp.js +3875 -0
- package/src/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/attachments-BOv1rBKp.js.map +1 -0
- package/src/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/constants-B9vgIdCd.js +21 -0
- package/src/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/constants-B9vgIdCd.js.map +1 -0
- package/src/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/dev-T1qbW_qD.js +1938 -0
- package/src/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/dev-T1qbW_qD.js.map +1 -0
- package/src/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/events-Bo11SfVK.js +205 -0
- package/src/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/events-Bo11SfVK.js.map +1 -0
- package/src/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/flags-X7D4vfrZ.js +18 -0
- package/src/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/flags-X7D4vfrZ.js.map +1 -0
- package/src/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/hydration-oyUje5i1.js +264 -0
- package/src/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/hydration-oyUje5i1.js.map +1 -0
- package/src/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/legacy-client-DeSQgSFm.js +697 -0
- package/src/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/legacy-client-DeSQgSFm.js.map +1 -0
- package/src/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/package.json +3 -0
- package/src/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte.js +134 -0
- package/src/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte.js.map +1 -0
- package/src/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_animate.js +55 -0
- package/src/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_animate.js.map +1 -0
- package/src/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_attachments.js +2 -0
- package/src/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_compiler.js +46843 -0
- package/src/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_compiler.js.map +1 -0
- package/src/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_easing.js +237 -0
- package/src/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_easing.js.map +1 -0
- package/src/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_events.js +2 -0
- package/src/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_internal.js +4 -0
- package/src/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_internal.js.map +1 -0
- package/src/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_internal_client.js +7 -0
- package/src/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_internal_disclose-version.js +4 -0
- package/src/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_internal_disclose-version.js.map +1 -0
- package/src/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_internal_flags_async.js +5 -0
- package/src/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_internal_flags_async.js.map +1 -0
- package/src/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_internal_flags_legacy.js +5 -0
- package/src/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_internal_flags_legacy.js.map +1 -0
- package/src/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_internal_flags_tracing.js +5 -0
- package/src/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_internal_flags_tracing.js.map +1 -0
- package/src/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_internal_server.js +6 -0
- package/src/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_legacy.js +83 -0
- package/src/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_legacy.js.map +1 -0
- package/src/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_motion.js +559 -0
- package/src/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_motion.js.map +1 -0
- package/src/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_reactivity.js +24 -0
- package/src/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_reactivity.js.map +1 -0
- package/src/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_reactivity_window.js +122 -0
- package/src/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_reactivity_window.js.map +1 -0
- package/src/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_server.js +2 -0
- package/src/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_store.js +89 -0
- package/src/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_store.js.map +1 -0
- package/src/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_transition.js +241 -0
- package/src/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_transition.js.map +1 -0
- package/src/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/utils-BbWyXNk1.js +4938 -0
- package/src/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/utils-BbWyXNk1.js.map +1 -0
- package/src/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/utils-DI_fC7vt.js +495 -0
- package/src/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/utils-DI_fC7vt.js.map +1 -0
- package/src/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/validate-Dyr3lQv9.js +190 -0
- package/src/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/validate-Dyr3lQv9.js.map +1 -0
- package/src/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/version-_9FHH-LF.js +8 -0
- package/src/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/version-_9FHH-LF.js.map +1 -0
- package/src/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/warnings-CUbSPnyP.js +138 -0
- package/src/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/warnings-CUbSPnyP.js.map +1 -0
- package/src/studio/project.test.ts +30 -0
- package/src/studio/project.ts +72 -8
- package/src/studio/samples/crm.ts +11 -2
- package/src/studio/samples/insurance.ts +10 -4
- package/src/studio/samples/samples.test.ts +36 -0
- package/src/studio/samples/shared.ts +14 -0
- package/src/studio/samples/support.ts +12 -5
- package/src/studio/screen-suites.test.ts +43 -0
- package/src/studio/screen-suites.ts +5 -2
- package/src/studio/user-error.ts +31 -0
- package/src/upgrade-prompt.ts +23 -10
- package/src/version.test.ts +17 -0
- package/src/version.ts +13 -0
- package/dist/designer/assets/index-DjPp1mn7.css +0 -1
- package/dist/designer/assets/src-BRKu53tV.js +0 -2842
- package/dist/designer/assets/src-DseB1VRN.css +0 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { EntitySchema } from '../../schema.js'
|
|
2
2
|
import type { FormatRule } from '../project.js'
|
|
3
|
-
import { screen, formScreen, boardScreen, calendarScreen, detailScreen, project, dashScreen, statusPills, pad, ids, type SampleApp } from './shared.js'
|
|
3
|
+
import { screen, formScreen, boardScreen, calendarScreen, detailScreen, project, dashScreen, statusPills, pad, ids, whenIs, type SampleApp } from './shared.js'
|
|
4
4
|
|
|
5
5
|
// SAP Fiori-style chrome: a dark shell bar across the top (the toolbar), a white
|
|
6
6
|
// launchpad-style rail, and a blue left-bar on the active item (square corners).
|
|
@@ -88,8 +88,14 @@ const claims: EntitySchema = {
|
|
|
88
88
|
{ value: 'paid', label: 'Paid', color: '#10b981' },
|
|
89
89
|
{ value: 'denied', label: 'Denied', color: '#ef4444' },
|
|
90
90
|
] },
|
|
91
|
-
|
|
92
|
-
|
|
91
|
+
// Money is frozen once the claim has been paid out - the figures stay on
|
|
92
|
+
// screen for reference, but they can no longer be edited.
|
|
93
|
+
{ field: 'amount', type: 'number', label: 'Amount ($)', min: 0, required: true, when: { disabled: whenIs('status', 'paid') } },
|
|
94
|
+
{ field: 'deductible', type: 'number', label: 'Deductible ($)', min: 0, defaultValue: 500, when: { disabled: whenIs('status', 'paid') } },
|
|
95
|
+
// A denial has to be justified, and only a denial asks for it.
|
|
96
|
+
{ field: 'denialReason', type: 'text', label: 'Reason for denial', hidden: { grid: true },
|
|
97
|
+
input: { editorType: 'textarea', span: 2, help: 'Required to deny a claim' },
|
|
98
|
+
when: { visible: whenIs('status', 'denied'), required: whenIs('status', 'denied') } },
|
|
93
99
|
// Derived: net payout after the deductible (display-only, never stored).
|
|
94
100
|
{ field: 'net', type: 'number', label: 'Net payout ($)', readonly: true, formula: 'amount - deductible' },
|
|
95
101
|
{ field: 'severity', type: 'number', label: 'Severity', min: 0, max: 100, defaultValue: 40, input: { editorType: 'slider', suffix: '%', help: 'Loss severity 0-100' } },
|
|
@@ -123,7 +129,7 @@ const seed = {
|
|
|
123
129
|
{ id: 'cl1', claimNumber: 'CLM-500001', policyId: 'pl1', adjusterId: 'ad1', type: 'collision', status: 'paid', amount: 8400, deductible: 500, severity: 55, fraudRisk: 1, tags: ['rear-end', 'no-injury'], incidentDate: '2026-01-04', filedAt: '2026-01-06T09:30:00.000Z' },
|
|
124
130
|
{ id: 'cl2', claimNumber: 'CLM-500002', policyId: 'pl2', adjusterId: 'ad2', type: 'water', status: 'review', amount: 15200, deductible: 1000, severity: 70, fraudRisk: 2, tags: ['burst-pipe'], incidentDate: '2026-02-01', filedAt: '2026-02-03T14:15:00.000Z' },
|
|
125
131
|
{ id: 'cl3', claimNumber: 'CLM-500003', policyId: 'pl3', adjusterId: 'ad3', type: 'medical', status: 'approved', amount: 6200, deductible: 750, severity: 40, fraudRisk: 1, tags: ['surgery'], incidentDate: '2026-02-10', filedAt: '2026-02-12T11:00:00.000Z' },
|
|
126
|
-
{ id: 'cl4', claimNumber: 'CLM-500004', policyId: 'pl5', adjusterId: 'ad1', type: 'theft', status: 'denied', amount: 22000, deductible: 500, severity: 90, fraudRisk: 5, tags: ['suspicious', 'late-report'], incidentDate: '2026-01-20', filedAt: '2026-01-28T16:45:00.000Z' },
|
|
132
|
+
{ id: 'cl4', claimNumber: 'CLM-500004', policyId: 'pl5', adjusterId: 'ad1', type: 'theft', status: 'denied', amount: 22000, deductible: 500, severity: 90, fraudRisk: 5, tags: ['suspicious', 'late-report'], incidentDate: '2026-01-20', filedAt: '2026-01-28T16:45:00.000Z', denialReason: 'Loss reported 8 days after the incident, outside the policy window.' },
|
|
127
133
|
{ id: 'cl5', claimNumber: 'CLM-500005', policyId: 'pl6', adjusterId: 'ad4', type: 'fire', status: 'paid', amount: 41000, deductible: 2000, severity: 95, fraudRisk: 2, tags: ['kitchen', 'total-loss'], incidentDate: '2026-03-01', filedAt: '2026-03-02T08:20:00.000Z' },
|
|
128
134
|
{ id: 'cl6', claimNumber: 'CLM-500006', policyId: 'pl7', adjusterId: 'ad3', type: 'medical', status: 'filed', amount: 3100, deductible: 750, severity: 25, fraudRisk: 1, tags: ['ER-visit'], incidentDate: '2026-03-11', filedAt: '2026-03-12T13:10:00.000Z' },
|
|
129
135
|
{ id: 'cl7', claimNumber: 'CLM-500007', policyId: 'pl8', adjusterId: 'ad6', type: 'collision', status: 'review', amount: 9700, deductible: 500, severity: 60, fraudRisk: 3, tags: ['intersection'], incidentDate: '2026-03-15', filedAt: '2026-03-17T10:05:00.000Z' },
|
|
@@ -214,6 +214,42 @@ describe('sample apps', () => {
|
|
|
214
214
|
}
|
|
215
215
|
})
|
|
216
216
|
})
|
|
217
|
+
|
|
218
|
+
describe('forms that react to the answers', () => {
|
|
219
|
+
// Each of these asks a question only when the answer matters: a lost deal
|
|
220
|
+
// wants a reason, a closed ticket wants its resolution, a denied claim wants
|
|
221
|
+
// a justification.
|
|
222
|
+
const cases: Record<string, { field: string; drivenBy: string }> = {
|
|
223
|
+
crm: { field: 'lostReason', drivenBy: 'stage' },
|
|
224
|
+
support: { field: 'resolution', drivenBy: 'status' },
|
|
225
|
+
insurance: { field: 'denialReason', drivenBy: 'status' },
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
for (const [id, { field, drivenBy }] of Object.entries(cases)) {
|
|
229
|
+
it(`${id}: asks for "${field}" only when "${drivenBy}" calls for it`, () => {
|
|
230
|
+
const project = getSampleApp(id)!.build()
|
|
231
|
+
const conditional = project.entities.flatMap((e) => e.fields).find((f) => f.field === field)!
|
|
232
|
+
expect(conditional.when?.visible, `${field} should be conditional`).toBeTruthy()
|
|
233
|
+
expect(conditional.when?.required, `${field} should be demanded when shown`).toBeTruthy()
|
|
234
|
+
// The condition has to read a field that exists, or it would sit hidden
|
|
235
|
+
// forever - validateProject enforces this, so assert it stays clean.
|
|
236
|
+
expect(validateProject(project).filter((i) => i.level === 'error')).toEqual([])
|
|
237
|
+
// And it must survive into the generated app as plain data.
|
|
238
|
+
const schemas = emitStudioProject(project).find((f) => f.path === 'src/lib/schemas.ts')!.contents
|
|
239
|
+
expect(schemas).toContain(`"${field}"`)
|
|
240
|
+
expect(schemas).toMatch(new RegExp(`"column": "${drivenBy}"`))
|
|
241
|
+
})
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
it('seeds the conditional fields, so a loaded sample shows real answers', () => {
|
|
245
|
+
const rowsOf = (id: string, entity: string) =>
|
|
246
|
+
(getSampleApp(id)!.build().dataSources?.[entity] as { seed?: Record<string, unknown>[] })?.seed ?? []
|
|
247
|
+
expect(rowsOf('crm', 'deals').filter((r) => r.stage === 'lost').every((r) => !!r.lostReason)).toBe(true)
|
|
248
|
+
expect(rowsOf('support', 'tickets').filter((r) => r.status === 'resolved' || r.status === 'closed')
|
|
249
|
+
.every((r) => !!r.resolution)).toBe(true)
|
|
250
|
+
expect(rowsOf('insurance', 'claims').filter((r) => r.status === 'denied').every((r) => !!r.denialReason)).toBe(true)
|
|
251
|
+
})
|
|
252
|
+
})
|
|
217
253
|
})
|
|
218
254
|
|
|
219
255
|
describe('starter project (fresh `svgrid-studio designer` session)', () => {
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
* parse/serialize and generates a runnable app with no special-casing.
|
|
9
9
|
*/
|
|
10
10
|
import type { EntitySchema } from '../../schema.js'
|
|
11
|
+
import type { PredicateExpr } from '../../expressions/expression-types.js'
|
|
11
12
|
import { generateValue } from '../sample-data.js'
|
|
12
13
|
import { gridConfig, type GridOpts } from '../screen-suites.js'
|
|
13
14
|
import { sanitizeProject, screenFromTemplate, buildDockLayout, type Block, type GridConfig, type Screen, type ScreenTemplate, type StudioProject, type EntityDataSource, type ShellStyle, type SchedulerViewConfig, type SchedulerViewMode, type AuthConfig, type AccessControl } from '../project.js'
|
|
@@ -61,6 +62,19 @@ export function pad(schema: EntitySchema, rows: Row[], target: number, fkPools:
|
|
|
61
62
|
/** The `id` values of a seed array (for building foreign-key pools). */
|
|
62
63
|
export const ids = (rows: Row[]): string[] => rows.map((r) => String(r.id))
|
|
63
64
|
|
|
65
|
+
/**
|
|
66
|
+
* `field = value` as a form condition (see `EntityField.when`). Helpers rather
|
|
67
|
+
* than hand-written literals because an operator the filter engine does not know
|
|
68
|
+
* silently matches everything, which would leave a conditional field permanently
|
|
69
|
+
* on-screen.
|
|
70
|
+
*/
|
|
71
|
+
export const whenIs = (field: string, value: string | number): PredicateExpr =>
|
|
72
|
+
({ kind: 'cmp', column: field, op: 'equals', value })
|
|
73
|
+
|
|
74
|
+
/** `field` is any one of `values`. */
|
|
75
|
+
export const whenIsAny = (field: string, values: ReadonlyArray<string | number>): PredicateExpr =>
|
|
76
|
+
({ kind: 'or', parts: values.map((value) => ({ kind: 'cmp', column: field, op: 'equals', value })) })
|
|
77
|
+
|
|
64
78
|
/** A ready-made app shown in the gallery. */
|
|
65
79
|
export type SampleApp = {
|
|
66
80
|
/** Stable id (also the `--template` value). */
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { EntitySchema } from '../../schema.js'
|
|
2
|
-
import { screen, formScreen, boardScreen, workspaceScreen, detailScreen, project, dashScreen, statusPills, pad, ids, type SampleApp } from './shared.js'
|
|
2
|
+
import { screen, formScreen, boardScreen, workspaceScreen, detailScreen, project, dashScreen, statusPills, pad, ids, whenIsAny, type SampleApp } from './shared.js'
|
|
3
3
|
|
|
4
4
|
// Outlook-style chrome: a solid blue command ribbon (the toolbar), a light "folder"
|
|
5
5
|
// rail with compact rows, and an accented left bar on the active folder.
|
|
@@ -86,7 +86,14 @@ const tickets: EntitySchema = {
|
|
|
86
86
|
{ field: 'tags', type: 'text', label: 'Tags', input: { editorType: 'chips', help: 'Topic labels for routing' } },
|
|
87
87
|
{ field: 'createdAt', type: 'datetime', label: 'Created', input: { editorType: 'datetime', help: 'When the ticket was opened' } },
|
|
88
88
|
{ field: 'firstResponseMins', type: 'number', label: 'First response (min)', min: 0, input: { help: 'Time to the first agent reply' } },
|
|
89
|
-
|
|
89
|
+
// The close notes appear the moment a ticket reaches a terminal status, and
|
|
90
|
+
// the ticket cannot be saved in that state without them.
|
|
91
|
+
{ field: 'resolution', type: 'text', label: 'Resolution', hidden: { grid: true },
|
|
92
|
+
input: { editorType: 'textarea', span: 2, help: 'What fixed it - required to resolve or close' },
|
|
93
|
+
when: { visible: whenIsAny('status', ['resolved', 'closed']), required: whenIsAny('status', ['resolved', 'closed']) } },
|
|
94
|
+
{ field: 'satisfaction', type: 'number', label: 'CSAT', min: 1, max: 5, input: { editorType: 'rating', help: 'Customer rating on close' },
|
|
95
|
+
// A rating only means something once the customer has had an outcome.
|
|
96
|
+
when: { visible: whenIsAny('status', ['resolved', 'closed']) } },
|
|
90
97
|
{ field: 'estimateHrs', type: 'number', label: 'Estimate (hrs)', min: 0, defaultValue: 1, input: { help: 'Estimated effort to resolve' } },
|
|
91
98
|
{ field: 'hourlyRate', type: 'number', label: 'Rate ($/hr)', min: 0, defaultValue: 90, input: { help: 'Billed support rate' } },
|
|
92
99
|
// Derived: estimated support cost for the ticket (display-only, never seeded).
|
|
@@ -111,10 +118,10 @@ const seed = {
|
|
|
111
118
|
{ id: 'tk1', subject: 'Cannot reset password', customerId: 'c1', agentId: 'a1', priority: 'high', status: 'open', channel: 'email', tags: ['auth', 'login'], createdAt: '2026-07-10T08:30:00Z', firstResponseMins: 12, satisfaction: 4, estimateHrs: 2, hourlyRate: 90 },
|
|
112
119
|
{ id: 'tk2', subject: 'Invoice discrepancy', customerId: 'c2', agentId: 'a2', priority: 'normal', status: 'pending', channel: 'email', tags: ['billing'], createdAt: '2026-07-08T11:05:00Z', firstResponseMins: 45, satisfaction: 3, estimateHrs: 1, hourlyRate: 75 },
|
|
113
120
|
{ id: 'tk3', subject: 'API returns 500', customerId: 'c3', agentId: 'a1', priority: 'urgent', status: 'open', channel: 'chat', tags: ['api', 'outage'], createdAt: '2026-07-12T15:40:00Z', firstResponseMins: 4, satisfaction: 5, estimateHrs: 4, hourlyRate: 120 },
|
|
114
|
-
{ id: 'tk4', subject: 'How do I export data?', customerId: 'c4', agentId: 'a3', priority: 'low', status: 'resolved', channel: 'chat', tags: ['export'], createdAt: '2026-07-02T09:20:00Z', firstResponseMins: 20, satisfaction: 5, estimateHrs: 1, hourlyRate: 60 },
|
|
115
|
-
{ id: 'tk5', subject: 'Upgrade to Business', customerId: 'c5', agentId: 'a3', priority: 'normal', status: 'closed', channel: 'phone', tags: ['upgrade', 'billing'], createdAt: '2026-06-28T13:10:00Z', firstResponseMins: 8, satisfaction: 4, estimateHrs: 1, hourlyRate: 75 },
|
|
121
|
+
{ id: 'tk4', subject: 'How do I export data?', customerId: 'c4', agentId: 'a3', priority: 'low', status: 'resolved', channel: 'chat', tags: ['export'], createdAt: '2026-07-02T09:20:00Z', firstResponseMins: 20, satisfaction: 5, estimateHrs: 1, hourlyRate: 60, resolution: 'Walked the customer through Export > CSV in the toolbar.' },
|
|
122
|
+
{ id: 'tk5', subject: 'Upgrade to Business', customerId: 'c5', agentId: 'a3', priority: 'normal', status: 'closed', channel: 'phone', tags: ['upgrade', 'billing'], createdAt: '2026-06-28T13:10:00Z', firstResponseMins: 8, satisfaction: 4, estimateHrs: 1, hourlyRate: 75, resolution: 'Upgraded the account to Business and prorated the invoice.' },
|
|
116
123
|
{ id: 'tk6', subject: 'SSO configuration', customerId: 'c1', agentId: 'a1', priority: 'high', status: 'pending', channel: 'email', tags: ['sso', 'security'], createdAt: '2026-07-11T10:00:00Z', firstResponseMins: 60, satisfaction: 2, estimateHrs: 3, hourlyRate: 120 },
|
|
117
|
-
{ id: 'tk7', subject: 'Double charge refund', customerId: 'c2', agentId: 'a2', priority: 'urgent', status: 'resolved', channel: 'phone', tags: ['refund', 'billing'], createdAt: '2026-07-05T16:25:00Z', firstResponseMins: 15, satisfaction: 4, estimateHrs: 2, hourlyRate: 90 },
|
|
124
|
+
{ id: 'tk7', subject: 'Double charge refund', customerId: 'c2', agentId: 'a2', priority: 'urgent', status: 'resolved', channel: 'phone', tags: ['refund', 'billing'], createdAt: '2026-07-05T16:25:00Z', firstResponseMins: 15, satisfaction: 4, estimateHrs: 2, hourlyRate: 90, resolution: 'Refunded the duplicate charge; payment retry guard added.' },
|
|
118
125
|
{ id: 'tk8', subject: 'Feature request: dark mode', customerId: 'c3', agentId: 'a3', priority: 'low', status: 'open', channel: 'chat', tags: ['feature'], createdAt: '2026-07-13T12:50:00Z', firstResponseMins: 90, satisfaction: 3, estimateHrs: 5, hourlyRate: 100 },
|
|
119
126
|
],
|
|
120
127
|
}
|
|
@@ -5,7 +5,9 @@ import {
|
|
|
5
5
|
createProject,
|
|
6
6
|
parseProject,
|
|
7
7
|
serializeProject,
|
|
8
|
+
ssrEligible,
|
|
8
9
|
validateProject,
|
|
10
|
+
withSsrDefaults,
|
|
9
11
|
type DetailConfig,
|
|
10
12
|
type GridConfig,
|
|
11
13
|
type Screen,
|
|
@@ -196,6 +198,47 @@ describe('crudAppFromSchemas', () => {
|
|
|
196
198
|
expect(app.dataSource).toBe('sql')
|
|
197
199
|
})
|
|
198
200
|
|
|
201
|
+
describe('server rendering defaults', () => {
|
|
202
|
+
const sqlApp = () => crudAppFromSchemas([customers, orders], {
|
|
203
|
+
dataSource: 'sql',
|
|
204
|
+
sources: {
|
|
205
|
+
customers: { kind: 'sql', table: 'customers', dialect: 'postgres' },
|
|
206
|
+
orders: { kind: 'sql', table: 'orders', dialect: 'postgres' },
|
|
207
|
+
},
|
|
208
|
+
})
|
|
209
|
+
|
|
210
|
+
it('renders database-backed screens on the server', () => {
|
|
211
|
+
const app = sqlApp()
|
|
212
|
+
const list = app.screens.find((s) => s.id === 'customers')!
|
|
213
|
+
expect(list.renderMode).toBe('ssr')
|
|
214
|
+
// Every screen that opted in has to actually be emittable that way.
|
|
215
|
+
for (const s of app.screens.filter((s) => s.renderMode === 'ssr')) {
|
|
216
|
+
expect(ssrEligible(app, s), `${s.id} claims ssr but is not eligible`).toBe(true)
|
|
217
|
+
}
|
|
218
|
+
})
|
|
219
|
+
|
|
220
|
+
it('leaves in-memory apps as a client SPA', () => {
|
|
221
|
+
// In-memory sources are per-process singletons: an SSR screen would mutate
|
|
222
|
+
// the server's rows while the app's SPA screens read the browser's.
|
|
223
|
+
const app = crudAppFromSchemas([customers, orders])
|
|
224
|
+
expect(app.screens.every((s) => s.renderMode === undefined)).toBe(true)
|
|
225
|
+
})
|
|
226
|
+
|
|
227
|
+
it('leaves PGlite alone even in a SQL project', () => {
|
|
228
|
+
const app = crudAppFromSchemas([customers], {
|
|
229
|
+
dataSource: 'sql',
|
|
230
|
+
sources: { customers: { kind: 'pglite', table: 'customers' } },
|
|
231
|
+
})
|
|
232
|
+
expect(app.screens.every((s) => s.renderMode === undefined)).toBe(true)
|
|
233
|
+
})
|
|
234
|
+
|
|
235
|
+
it('never overrides a mode that is already set', () => {
|
|
236
|
+
const app = sqlApp()
|
|
237
|
+
const pinned = { ...app, screens: app.screens.map((s) => ({ ...s, renderMode: 'spa' as const })) }
|
|
238
|
+
expect(withSsrDefaults(pinned).screens.every((s) => s.renderMode === 'spa')).toBe(true)
|
|
239
|
+
})
|
|
240
|
+
})
|
|
241
|
+
|
|
199
242
|
it('applies per-entity overrides', () => {
|
|
200
243
|
const app = crudAppFromSchemas([customers, orders], {
|
|
201
244
|
overviewDashboard: false,
|
|
@@ -25,6 +25,7 @@ import {
|
|
|
25
25
|
entityOf,
|
|
26
26
|
pickFacetFields,
|
|
27
27
|
sanitizeProject,
|
|
28
|
+
withSsrDefaults,
|
|
28
29
|
type Block,
|
|
29
30
|
type BlockConfig,
|
|
30
31
|
type DataSourceKind,
|
|
@@ -434,12 +435,14 @@ export function crudAppFromSchemas(schemas: EntitySchema[], opts: CrudAppOptions
|
|
|
434
435
|
: source
|
|
435
436
|
}
|
|
436
437
|
|
|
437
|
-
|
|
438
|
+
// Database-backed screens render on the server by default, so a generated app
|
|
439
|
+
// ships real SvelteKit (load + form actions) rather than a client SPA.
|
|
440
|
+
return withSsrDefaults(sanitizeProject({
|
|
438
441
|
title: opts.title ?? 'My Studio App',
|
|
439
442
|
entities: [...schemas],
|
|
440
443
|
screens,
|
|
441
444
|
dataSource: kind,
|
|
442
445
|
dataSources,
|
|
443
446
|
...(opts.theme ? { theme: opts.theme } : {}),
|
|
444
|
-
})
|
|
447
|
+
}))
|
|
445
448
|
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* UserError - a mistake the person at the keyboard can fix (a typo in a
|
|
3
|
+
* connection string, an empty table selection, a folder that already holds
|
|
4
|
+
* another app), as opposed to a defect in Studio.
|
|
5
|
+
*
|
|
6
|
+
* The CLI prints these plainly and stops. Everything else is treated as a crash
|
|
7
|
+
* and gets a sanitized bug report plus a prefilled issue link, so ordinary
|
|
8
|
+
* fat-fingering never looks like something worth reporting.
|
|
9
|
+
*
|
|
10
|
+
* `isUserError` is duck-typed rather than an `instanceof` check: the class can
|
|
11
|
+
* cross a bundle boundary (browser ESM source vs. the Node build), and a
|
|
12
|
+
* mis-detected user error would send someone to file an issue about their own
|
|
13
|
+
* typo.
|
|
14
|
+
*/
|
|
15
|
+
export class UserError extends Error {
|
|
16
|
+
/** Marker read by `isUserError`; survives bundling and subclassing. */
|
|
17
|
+
readonly isUserError = true
|
|
18
|
+
/** Optional follow-up: what to try instead. */
|
|
19
|
+
readonly hint?: string
|
|
20
|
+
|
|
21
|
+
constructor(message: string, hint?: string) {
|
|
22
|
+
super(message)
|
|
23
|
+
this.name = 'UserError'
|
|
24
|
+
if (hint !== undefined) this.hint = hint
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** True for anything thrown as a `UserError`, across module instances. */
|
|
29
|
+
export function isUserError(err: unknown): err is UserError {
|
|
30
|
+
return !!err && typeof err === 'object' && (err as { isUserError?: unknown }).isUserError === true
|
|
31
|
+
}
|
package/src/upgrade-prompt.ts
CHANGED
|
@@ -39,13 +39,16 @@ export type EnterpriseFeatureLabel =
|
|
|
39
39
|
* been shown this session, a card is already up, or we're on the server. Safe
|
|
40
40
|
* to call on every Pro feature invocation - it self-throttles.
|
|
41
41
|
*/
|
|
42
|
-
export function showUpgradePrompt(
|
|
42
|
+
export function showUpgradePrompt(
|
|
43
|
+
feature?: EnterpriseFeatureLabel | string,
|
|
44
|
+
opts?: { expired?: boolean },
|
|
45
|
+
): void {
|
|
43
46
|
if (shownThisSession) return
|
|
44
47
|
if (typeof document === 'undefined' || typeof window === 'undefined') return
|
|
45
48
|
if (document.querySelector(`[${CARD_ATTR}]`)) return
|
|
46
49
|
shownThisSession = true
|
|
47
50
|
|
|
48
|
-
const card = buildCard(feature)
|
|
51
|
+
const card = buildCard(feature, opts?.expired === true)
|
|
49
52
|
document.body.appendChild(card)
|
|
50
53
|
// Animate in on the next frame so the transition actually runs.
|
|
51
54
|
requestAnimationFrame(() => {
|
|
@@ -73,11 +76,11 @@ function close(card: HTMLElement): void {
|
|
|
73
76
|
window.setTimeout(() => card.remove(), 220)
|
|
74
77
|
}
|
|
75
78
|
|
|
76
|
-
function buildCard(feature?: EnterpriseFeatureLabel | string): HTMLElement {
|
|
79
|
+
function buildCard(feature?: EnterpriseFeatureLabel | string, expired = false): HTMLElement {
|
|
77
80
|
const card = document.createElement('div')
|
|
78
81
|
card.setAttribute(CARD_ATTR, '1')
|
|
79
82
|
card.setAttribute('role', 'dialog')
|
|
80
|
-
card.setAttribute('aria-label', 'Unlock SvGrid Pro')
|
|
83
|
+
card.setAttribute('aria-label', expired ? 'Your trial has ended' : 'Unlock SvGrid Pro')
|
|
81
84
|
Object.assign(card.style, {
|
|
82
85
|
position: 'fixed',
|
|
83
86
|
bottom: '16px',
|
|
@@ -105,24 +108,34 @@ function buildCard(feature?: EnterpriseFeatureLabel | string): HTMLElement {
|
|
|
105
108
|
? `<strong style="color:#fff">${escapeHtml(featureName)}</strong> is a Pro feature.`
|
|
106
109
|
: `You just used a <strong style="color:#fff">Pro</strong> feature.`
|
|
107
110
|
|
|
111
|
+
// An expired trial is a different message from "you never had a licence":
|
|
112
|
+
// they already evaluated, so the ask is to convert, not to start over.
|
|
113
|
+
const badge = expired ? 'TRIAL ENDED' : 'PRO'
|
|
114
|
+
const heading = expired ? 'Your trial has ended' : 'Unlock SvGrid Pro'
|
|
115
|
+
const body = expired
|
|
116
|
+
? `${featurePhrase} Your evaluation period is over - everything still works,
|
|
117
|
+
but this notice stays until a licence key is set.`
|
|
118
|
+
: `${featurePhrase} It runs in evaluation with a watermark - start a free trial
|
|
119
|
+
to remove it and ship to production.`
|
|
120
|
+
const cta = expired ? 'Buy a licence' : 'Start free trial'
|
|
121
|
+
|
|
108
122
|
card.innerHTML = `
|
|
109
123
|
<button type="button" data-act="x" aria-label="Dismiss"
|
|
110
124
|
style="position:absolute;top:8px;right:8px;width:24px;height:24px;display:flex;
|
|
111
125
|
align-items:center;justify-content:center;background:transparent;border:0;
|
|
112
126
|
color:#94a3b8;font-size:18px;line-height:1;cursor:pointer;border-radius:6px;">×</button>
|
|
113
127
|
<div style="display:flex;align-items:center;gap:8px;margin-bottom:8px;">
|
|
114
|
-
<span style="display:inline-block;background
|
|
115
|
-
font-weight:700;letter-spacing:0.06em;padding:2px 7px;border-radius:999px;"
|
|
116
|
-
<span style="font-weight:700;color:#fff;"
|
|
128
|
+
<span style="display:inline-block;background:${expired ? '#b45309' : '#4f46e5'};color:#fff;font-size:10px;
|
|
129
|
+
font-weight:700;letter-spacing:0.06em;padding:2px 7px;border-radius:999px;">${badge}</span>
|
|
130
|
+
<span style="font-weight:700;color:#fff;">${heading}</span>
|
|
117
131
|
</div>
|
|
118
132
|
<p style="margin:0 0 12px;color:#cbd5e1;">
|
|
119
|
-
${
|
|
120
|
-
to remove it and ship to production.
|
|
133
|
+
${body}
|
|
121
134
|
</p>
|
|
122
135
|
<div style="display:flex;gap:8px;align-items:center;">
|
|
123
136
|
<a data-act="trial" href="${TRIAL_URL}" target="_blank" rel="noopener noreferrer"
|
|
124
137
|
style="flex:1;text-align:center;background:#6366f1;color:#fff;font-weight:600;
|
|
125
|
-
text-decoration:none;padding:8px 12px;border-radius:8px;"
|
|
138
|
+
text-decoration:none;padding:8px 12px;border-radius:8px;">${cta}</a>
|
|
126
139
|
<a data-act="pricing" href="${PRICING_URL}" target="_blank" rel="noopener noreferrer"
|
|
127
140
|
style="color:#a5b4fc;text-decoration:none;padding:8px 6px;font-weight:600;">Pricing</a>
|
|
128
141
|
</div>
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest'
|
|
2
|
+
import { readFileSync } from 'node:fs'
|
|
3
|
+
import { resolve } from 'node:path'
|
|
4
|
+
import { SVGRID_VERSION } from './version'
|
|
5
|
+
|
|
6
|
+
describe('SVGRID_VERSION', () => {
|
|
7
|
+
it('matches package.json, so generated apps pin a version that exists', () => {
|
|
8
|
+
// Generated apps depend on `^SVGRID_VERSION`. If this drifts below the real
|
|
9
|
+
// version, every scaffolded app asks for a runtime older than the generator
|
|
10
|
+
// and can miss features the emitted code uses.
|
|
11
|
+
const pkg = JSON.parse(readFileSync(resolve(process.cwd(), 'package.json'), 'utf8')) as { name: string; version: string }
|
|
12
|
+
// Guard the guard: if the runner's cwd ever moves, fail loudly here rather
|
|
13
|
+
// than quietly comparing against some other package's version.
|
|
14
|
+
expect(pkg.name).toBe('@svgrid/enterprise')
|
|
15
|
+
expect(SVGRID_VERSION).toBe(pkg.version)
|
|
16
|
+
})
|
|
17
|
+
})
|
package/src/version.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The version of this package, as a value the pure (browser-safe) generator can
|
|
3
|
+
* read - it cannot open `package.json` at runtime.
|
|
4
|
+
*
|
|
5
|
+
* Generated apps depend on `^SVGRID_VERSION` rather than `latest`, so an app
|
|
6
|
+
* always installs a runtime at least as new as the generator that wrote it.
|
|
7
|
+
* `latest` made that a coin toss: an app scaffolded today and the same app
|
|
8
|
+
* scaffolded tomorrow could resolve different runtimes, and an unrelated publish
|
|
9
|
+
* could break an app nobody had touched.
|
|
10
|
+
*
|
|
11
|
+
* `version.test.ts` asserts this matches `package.json`, so the two cannot drift.
|
|
12
|
+
*/
|
|
13
|
+
export const SVGRID_VERSION = '2.4.0'
|