@abgov/nx-adsp 13.27.1 → 13.28.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@abgov/nx-adsp",
3
- "version": "13.27.1",
3
+ "version": "13.28.0",
4
4
  "license": "Apache-2.0",
5
5
  "main": "src/index.js",
6
6
  "description": "Government of Alberta - Nx plugin for ADSP apps.",
@@ -43,11 +43,13 @@ function default_1(host, options) {
43
43
  path: `${normalizedOptions.route}/:id`,
44
44
  componentImportPath: `../views/${normalizedOptions.editViewFileName}.vue`,
45
45
  requiresAuth: normalizedOptions.requiresAuth,
46
+ layout: 'form',
46
47
  });
47
48
  (0, vue_router_1.insertVueRoute)(host, normalizedOptions.projectRoot, normalizedOptions.project, {
48
49
  path: normalizedOptions.route,
49
50
  componentImportPath: `../views/${normalizedOptions.listViewFileName}.vue`,
50
51
  requiresAuth: normalizedOptions.requiresAuth,
52
+ layout: 'wide',
51
53
  });
52
54
  yield (0, devkit_1.formatFiles)(host);
53
55
  });
@@ -1 +1 @@
1
- {"version":3,"file":"vue-admin-crud.js","sourceRoot":"","sources":["../../../../../../packages/nx-adsp/src/generators/vue-admin-crud/vue-admin-crud.ts"],"names":[],"mappings":";;AAkDA,4BAiCC;;AAnFD,uCAMoB;AACpB,6BAA6B;AAC7B,uDAAwD;AACxD,qEAE0C;AAG1C,6EAA6E;AAC7E,4EAA4E;AAC5E,8EAA8E;AAC9E,2EAA2E;AAC3E,0EAA0E;AAC1E,6EAA6E;AAC7E,iEAAiE;AACjE,SAAS,WAAW,CAAC,MAAwB;IAC3C,MAAM,MAAM,GAAG,OAAO,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC;IACxE,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAClD,MAAM,IAAI,KAAK,CACb,sFAAsF,CACvF,CAAC;IACJ,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,SAAS,gBAAgB,CAAC,IAAU,EAAE,OAAe;;IACnD,MAAM,EAAE,IAAI,EAAE,WAAW,EAAE,GAAG,IAAA,iCAAwB,EAAC,IAAI,EAAE,OAAO,CAAC,OAAO,CAAC,CAAC;IAE9E,MAAM,SAAS,GAAG,IAAA,cAAK,EAAC,OAAO,CAAC,IAAI,CAAC,CAAC,SAAS,CAAC;IAChD,0EAA0E;IAC1E,+BAA+B;IAC/B,MAAM,OAAO,GAAG,MAAA,OAAO,CAAC,OAAO,mCAAI,SAAS,CAAC,OAAO,CAAC,UAAU,EAAE,KAAK,CAAC,CAAC,IAAI,EAAE,CAAC;IAC/E,uCACK,OAAO,KACV,WAAW,EACX,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,MAAM,CAAC,EACnC,gBAAgB,EAAE,GAAG,SAAS,UAAU,EACxC,gBAAgB,EAAE,GAAG,SAAS,UAAU,EACxC,OAAO,EACP,aAAa,EAAE,MAAA,OAAO,CAAC,aAAa,mCAAI,OAAO,EAC/C,YAAY,EAAE,MAAA,OAAO,CAAC,YAAY,mCAAI,IAAI,IAC1C;AACJ,CAAC;AAED,mBAA+B,IAAU,EAAE,OAAe;;QACxD,MAAM,iBAAiB,GAAG,gBAAgB,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;QAE1D,2EAA2E;QAC3E,oCAAoC;QACpC,MAAM,IAAA,wBAAsB,EAAC,IAAI,CAAC,CAAC;QAEnC,IAAA,sBAAa,EACX,IAAI,EACJ,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,OAAO,CAAC,EAC7B,iBAAiB,CAAC,WAAW,kCAExB,iBAAiB,KACpB,aAAa,EAAE,IAAA,wCAAuB,EAAC,IAAI,CAAC,EAC5C,IAAI,EAAE,EAAE,IAEX,CAAC;QAEF,yEAAyE;QACzE,sEAAsE;QACtE,uEAAuE;QACvE,IAAA,2BAAc,EAAC,IAAI,EAAE,iBAAiB,CAAC,WAAW,EAAE,iBAAiB,CAAC,OAAO,EAAE;YAC7E,IAAI,EAAE,GAAG,iBAAiB,CAAC,KAAK,MAAM;YACtC,mBAAmB,EAAE,YAAY,iBAAiB,CAAC,gBAAgB,MAAM;YACzE,YAAY,EAAE,iBAAiB,CAAC,YAAY;SAC7C,CAAC,CAAC;QACH,IAAA,2BAAc,EAAC,IAAI,EAAE,iBAAiB,CAAC,WAAW,EAAE,iBAAiB,CAAC,OAAO,EAAE;YAC7E,IAAI,EAAE,iBAAiB,CAAC,KAAK;YAC7B,mBAAmB,EAAE,YAAY,iBAAiB,CAAC,gBAAgB,MAAM;YACzE,YAAY,EAAE,iBAAiB,CAAC,YAAY;SAC7C,CAAC,CAAC;QAEH,MAAM,IAAA,oBAAW,EAAC,IAAI,CAAC,CAAC;IAC1B,CAAC;CAAA"}
1
+ {"version":3,"file":"vue-admin-crud.js","sourceRoot":"","sources":["../../../../../../packages/nx-adsp/src/generators/vue-admin-crud/vue-admin-crud.ts"],"names":[],"mappings":";;AAkDA,4BAmCC;;AArFD,uCAMoB;AACpB,6BAA6B;AAC7B,uDAAwD;AACxD,qEAE0C;AAG1C,6EAA6E;AAC7E,4EAA4E;AAC5E,8EAA8E;AAC9E,2EAA2E;AAC3E,0EAA0E;AAC1E,6EAA6E;AAC7E,iEAAiE;AACjE,SAAS,WAAW,CAAC,MAAwB;IAC3C,MAAM,MAAM,GAAG,OAAO,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC;IACxE,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAClD,MAAM,IAAI,KAAK,CACb,sFAAsF,CACvF,CAAC;IACJ,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,SAAS,gBAAgB,CAAC,IAAU,EAAE,OAAe;;IACnD,MAAM,EAAE,IAAI,EAAE,WAAW,EAAE,GAAG,IAAA,iCAAwB,EAAC,IAAI,EAAE,OAAO,CAAC,OAAO,CAAC,CAAC;IAE9E,MAAM,SAAS,GAAG,IAAA,cAAK,EAAC,OAAO,CAAC,IAAI,CAAC,CAAC,SAAS,CAAC;IAChD,0EAA0E;IAC1E,+BAA+B;IAC/B,MAAM,OAAO,GAAG,MAAA,OAAO,CAAC,OAAO,mCAAI,SAAS,CAAC,OAAO,CAAC,UAAU,EAAE,KAAK,CAAC,CAAC,IAAI,EAAE,CAAC;IAC/E,uCACK,OAAO,KACV,WAAW,EACX,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,MAAM,CAAC,EACnC,gBAAgB,EAAE,GAAG,SAAS,UAAU,EACxC,gBAAgB,EAAE,GAAG,SAAS,UAAU,EACxC,OAAO,EACP,aAAa,EAAE,MAAA,OAAO,CAAC,aAAa,mCAAI,OAAO,EAC/C,YAAY,EAAE,MAAA,OAAO,CAAC,YAAY,mCAAI,IAAI,IAC1C;AACJ,CAAC;AAED,mBAA+B,IAAU,EAAE,OAAe;;QACxD,MAAM,iBAAiB,GAAG,gBAAgB,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;QAE1D,2EAA2E;QAC3E,oCAAoC;QACpC,MAAM,IAAA,wBAAsB,EAAC,IAAI,CAAC,CAAC;QAEnC,IAAA,sBAAa,EACX,IAAI,EACJ,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,OAAO,CAAC,EAC7B,iBAAiB,CAAC,WAAW,kCAExB,iBAAiB,KACpB,aAAa,EAAE,IAAA,wCAAuB,EAAC,IAAI,CAAC,EAC5C,IAAI,EAAE,EAAE,IAEX,CAAC;QAEF,yEAAyE;QACzE,sEAAsE;QACtE,uEAAuE;QACvE,IAAA,2BAAc,EAAC,IAAI,EAAE,iBAAiB,CAAC,WAAW,EAAE,iBAAiB,CAAC,OAAO,EAAE;YAC7E,IAAI,EAAE,GAAG,iBAAiB,CAAC,KAAK,MAAM;YACtC,mBAAmB,EAAE,YAAY,iBAAiB,CAAC,gBAAgB,MAAM;YACzE,YAAY,EAAE,iBAAiB,CAAC,YAAY;YAC5C,MAAM,EAAE,MAAM;SACf,CAAC,CAAC;QACH,IAAA,2BAAc,EAAC,IAAI,EAAE,iBAAiB,CAAC,WAAW,EAAE,iBAAiB,CAAC,OAAO,EAAE;YAC7E,IAAI,EAAE,iBAAiB,CAAC,KAAK;YAC7B,mBAAmB,EAAE,YAAY,iBAAiB,CAAC,gBAAgB,MAAM;YACzE,YAAY,EAAE,iBAAiB,CAAC,YAAY;YAC5C,MAAM,EAAE,MAAM;SACf,CAAC,CAAC;QAEH,MAAM,IAAA,oBAAW,EAAC,IAAI,CAAC,CAAC;IAC1B,CAAC;CAAA"}
@@ -177,7 +177,7 @@ describe('Vue Admin CRUD Generator', () => {
177
177
  "component: () => import('../views/RegionsEditView.vue')",
178
178
  );
179
179
  expect(
180
- routerTs.split('meta: { requiresAuth: true }').length - 1,
180
+ routerTs.split('requiresAuth: true').length - 1,
181
181
  ).toBe(2);
182
182
  // The existing route is untouched, not replaced.
183
183
  expect(routerTs).toContain("{ path: '/', component: HomeView }");
@@ -0,0 +1,42 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { mount } from '@vue/test-utils';
3
+ import Stepper from './Stepper.vue';
4
+
5
+ // goa-form-stepper's own `step` prop (1-based) drives the progress bar and the
6
+ // current-step emphasis; goa-form-step's status enum has no "current" value.
7
+ // Without it the element defaults to 1 and every page of a wizard shows step one
8
+ // as current.
9
+ const steps = [
10
+ { key: 'a', label: 'A', status: 'complete' as const },
11
+ { key: 'b', label: 'B', status: 'incomplete' as const },
12
+ { key: 'c', label: 'C', status: 'not-started' as const },
13
+ ];
14
+
15
+ const stepAttr = (step?: number) =>
16
+ mount(Stepper, { props: step === undefined ? { steps } : { steps, step } })
17
+ .find('goa-form-stepper')
18
+ .attributes('step');
19
+
20
+ describe('Stepper', () => {
21
+ it('forwards the current step to goa-form-stepper', () => {
22
+ expect(stepAttr(2)).toBe('2');
23
+ expect(stepAttr(3)).toBe('3');
24
+ });
25
+
26
+ it('defaults to the first step', () => {
27
+ expect(stepAttr()).toBe('1');
28
+ });
29
+
30
+ it('clamps out of range rather than handing the element a bad progress value', () => {
31
+ // goa-form-stepper sets a <progress> value from this; out of range yields a
32
+ // non-finite value that throws in the browser.
33
+ expect(stepAttr(0)).toBe('1');
34
+ expect(stepAttr(-5)).toBe('1');
35
+ expect(stepAttr(99)).toBe('3');
36
+ });
37
+
38
+ it('clamps to 1 when there are no steps', () => {
39
+ const el = mount(Stepper, { props: { steps: [], step: 4 } }).find('goa-form-stepper');
40
+ expect(el.attributes('step')).toBe('1');
41
+ });
42
+ });
@@ -1,4 +1,5 @@
1
1
  <script setup lang="ts">
2
+ import { computed } from 'vue';
2
3
  // Wraps the real, native goa-form-stepper/goa-form-step pair (confirmed via
3
4
  // design.alberta.ca/components/form-stepper -- there wasn't one when the spec
4
5
  // this is based on was written, hence "nobody used a native GoA stepper"; that
@@ -17,12 +18,27 @@ export interface StepperStep {
17
18
  status: 'complete' | 'incomplete' | 'not-started';
18
19
  }
19
20
 
20
- const props = defineProps<{ steps: StepperStep[] }>();
21
+ // `step` is goa-form-stepper's OWN prop (1-based) and is what drives the
22
+ // progress bar and the current-step emphasis. Without it the element defaults
23
+ // to 1, so every page of a wizard renders step one as current and the internal
24
+ // <progress> sits at zero. goa-form-step's status enum has no "current" value —
25
+ // that state lives on the parent, which is easy to miss when reading only the
26
+ // child's API.
27
+ const props = withDefaults(
28
+ defineProps<{ steps: StepperStep[]; step?: number }>(),
29
+ { step: 1 },
30
+ );
31
+
32
+ // Clamped: the element sets a <progress> value from this, and an out-of-range
33
+ // number yields a non-finite value that throws in the browser.
34
+ const currentStep = computed(() =>
35
+ Math.min(Math.max(props.step, 1), Math.max(props.steps.length, 1)),
36
+ );
21
37
  const emit = defineEmits<{ select: [key: string] }>();
22
38
  </script>
23
39
 
24
40
  <template>
25
- <goa-form-stepper>
41
+ <goa-form-stepper :step="currentStep">
26
42
  <goa-form-step
27
43
  v-for="(step, index) in props.steps"
28
44
  :key="step.key"
@@ -93,6 +93,14 @@ describe('Vue Components Generator', () => {
93
93
  const formattersSrc = host
94
94
  .read('libs/vue-components/src/lib/formatters.ts')
95
95
  .toString();
96
+ // goa-form-stepper's `step` prop drives the progress bar; without it every
97
+ // page of a wizard renders step one as current.
98
+ const stepper = host
99
+ .read('libs/vue-components/src/lib/patterns/Stepper.vue')
100
+ .toString();
101
+ expect(stepper).toContain(':step="currentStep"');
102
+ expect(stepper).toContain('Math.min(Math.max(props.step, 1)');
103
+
96
104
  expect(formattersSrc).toContain('DATE_ONLY');
97
105
  expect(formattersSrc).toContain('local.getFullYear() === year');
98
106
 
@@ -121,6 +129,7 @@ describe('Vue Components Generator', () => {
121
129
  'libs/vue-components/src/lib/patterns/FilterBar.spec.ts',
122
130
  'libs/vue-components/src/lib/primitives/GoabDatePicker.spec.ts',
123
131
  'libs/vue-components/src/lib/patterns/WorkspaceTable.spec.ts',
132
+ 'libs/vue-components/src/lib/patterns/Stepper.spec.ts',
124
133
  ]) {
125
134
  expect(host.exists(spec)).toBeTruthy();
126
135
  }
@@ -153,6 +153,8 @@ describe('Vue Detail View Generator', () => {
153
153
  "component: () => import('../views/ApplicationDetailView.vue')",
154
154
  );
155
155
  expect(routerTs).toContain('meta: { requiresAuth: true }');
156
+ // A record info list is neither a table nor a form -- default width.
157
+ expect(routerTs).not.toContain('layout:');
156
158
  // The existing route is untouched, not replaced.
157
159
  expect(routerTs).toContain("{ path: '/', component: HomeView }");
158
160
  }, 30000);
@@ -145,7 +145,7 @@ function goBack() {
145
145
 
146
146
  <template>
147
147
  <div>
148
- <Stepper :steps="stepperSteps" @select="onStepperSelect" />
148
+ <Stepper :steps="stepperSteps" :step="<%= stepNumber %>" @select="onStepperSelect" />
149
149
 
150
150
  <goa-spacer vspacing="l" />
151
151
 
@@ -53,7 +53,11 @@ function default_1(host, options) {
53
53
  steps.forEach((step, index) => {
54
54
  const nextStepKey = index === steps.length - 1 ? 'review' : steps[index + 1].key;
55
55
  (0, devkit_1.generateFiles)(host, path.join(__dirname, 'files/steps'), projectRoot, Object.assign(Object.assign({}, normalizedOptions), { goaImportPath, stepViewFileName: stepViewFileName(step.key), stepKey: step.key, stepLabel: step.label, stepFields: step.fields, stepperSteps,
56
- nextStepKey, tmpl: '' }));
56
+ nextStepKey,
57
+ // 1-based, for goa-form-stepper's own `step` prop. The generator knows
58
+ // which step it is emitting; the view would otherwise have to re-derive
59
+ // it from the route.
60
+ stepNumber: index + 1, tmpl: '' }));
57
61
  });
58
62
  (0, devkit_1.generateFiles)(host, path.join(__dirname, 'files/shared'), projectRoot, Object.assign(Object.assign({}, normalizedOptions), { goaImportPath,
59
63
  reviewViewFileName,
@@ -70,6 +74,7 @@ function default_1(host, options) {
70
74
  path: `${normalizedOptions.route}/:id/review`,
71
75
  componentImportPath: `../views/${reviewViewFileName}.vue`,
72
76
  requiresAuth: normalizedOptions.requiresAuth,
77
+ layout: 'form',
73
78
  });
74
79
  for (let i = steps.length - 1; i >= 0; i--) {
75
80
  const step = steps[i];
@@ -77,6 +82,7 @@ function default_1(host, options) {
77
82
  path: `${normalizedOptions.route}/:id/${step.key}`,
78
83
  componentImportPath: `../views/${stepViewFileName(step.key)}.vue`,
79
84
  requiresAuth: normalizedOptions.requiresAuth,
85
+ layout: 'form',
80
86
  });
81
87
  }
82
88
  yield (0, devkit_1.formatFiles)(host);
@@ -1 +1 @@
1
- {"version":3,"file":"vue-intake-view.js","sourceRoot":"","sources":["../../../../../../packages/nx-adsp/src/generators/vue-intake-view/vue-intake-view.ts"],"names":[],"mappings":";;AAsDA,4BAsEC;;AA5HD,uCAMoB;AACpB,6BAA6B;AAC7B,uDAAwD;AACxD,qEAE0C;AAG1C,6EAA6E;AAC7E,4EAA4E;AAC5E,6EAA6E;AAC7E,2EAA2E;AAC3E,0EAA0E;AAC1E,4EAA4E;AAC5E,iEAAiE;AACjE,SAAS,UAAU,CAAC,KAAsB;IACxC,MAAM,MAAM,GAAG,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC;IACrE,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAClD,MAAM,IAAI,KAAK,CACb,2EAA2E,CAC5E,CAAC;IACJ,CAAC;IACD,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;IAC/B,KAAK,MAAM,IAAI,IAAI,MAAM,EAAE,CAAC;QAC1B,IAAI,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;YACvB,MAAM,IAAI,KAAK,CAAC,sCAAsC,IAAI,CAAC,GAAG,IAAI,CAAC,CAAC;QACtE,CAAC;QACD,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IACrB,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,SAAS,gBAAgB,CAAC,IAAU,EAAE,OAAe;;IACnD,MAAM,EAAE,IAAI,EAAE,WAAW,EAAE,GAAG,IAAA,iCAAwB,EAAC,IAAI,EAAE,OAAO,CAAC,OAAO,CAAC,CAAC;IAC9E,uCACK,OAAO,KACV,WAAW,EACX,KAAK,EAAE,UAAU,CAAC,OAAO,CAAC,KAAK,CAAC,EAChC,YAAY,EAAE,MAAA,OAAO,CAAC,YAAY,mCAAI,IAAI,EAC1C,QAAQ,EAAE,IAAA,cAAK,EAAC,OAAO,CAAC,IAAI,CAAC,CAAC,SAAS,IACvC;AACJ,CAAC;AAED,mFAAmF;AACnF,SAAS,gBAAgB,CAAC,OAAe;IACvC,OAAO,GAAG,IAAA,cAAK,EAAC,OAAO,CAAC,CAAC,SAAS,UAAU,CAAC;AAC/C,CAAC;AAED,mBAA+B,IAAU,EAAE,OAAe;;QACxD,MAAM,iBAAiB,GAAG,gBAAgB,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;QAC1D,MAAM,EAAE,WAAW,EAAE,KAAK,EAAE,QAAQ,EAAE,GAAG,iBAAiB,CAAC;QAE3D,yEAAyE;QACzE,iDAAiD;QACjD,MAAM,IAAA,wBAAsB,EAAC,IAAI,CAAC,CAAC;QAEnC,MAAM,aAAa,GAAG,IAAA,wCAAuB,EAAC,IAAI,CAAC,CAAC;QACpD,MAAM,kBAAkB,GAAG,GAAG,QAAQ,YAAY,CAAC;QACnD,MAAM,wBAAwB,GAAG,GAAG,QAAQ,kBAAkB,CAAC;QAE/D,MAAM,YAAY,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;YACxC,GAAG,EAAE,IAAI,CAAC,GAAG;YACb,KAAK,EAAE,IAAI,CAAC,KAAK;SAClB,CAAC,CAAC,CAAC;QAEJ,KAAK,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE;YAC5B,MAAM,WAAW,GACf,KAAK,KAAK,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC;YAE/D,IAAA,sBAAa,EACX,IAAI,EACJ,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,aAAa,CAAC,EACnC,WAAW,kCAEN,iBAAiB,KACpB,aAAa,EACb,gBAAgB,EAAE,gBAAgB,CAAC,IAAI,CAAC,GAAG,CAAC,EAC5C,OAAO,EAAE,IAAI,CAAC,GAAG,EACjB,SAAS,EAAE,IAAI,CAAC,KAAK,EACrB,UAAU,EAAE,IAAI,CAAC,MAAM,EACvB,YAAY;gBACZ,WAAW,EACX,IAAI,EAAE,EAAE,IAEX,CAAC;QACJ,CAAC,CAAC,CAAC;QAEH,IAAA,sBAAa,EAAC,IAAI,EAAE,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,cAAc,CAAC,EAAE,WAAW,kCAChE,iBAAiB,KACpB,aAAa;YACb,kBAAkB;YAClB,wBAAwB,EACxB,IAAI,EAAE,EAAE,IACR,CAAC;QAEH,wEAAwE;QACxE,wEAAwE;QACxE,4BAA4B;QAC5B,IAAA,2BAAc,EAAC,IAAI,EAAE,WAAW,EAAE,OAAO,CAAC,OAAO,EAAE;YACjD,IAAI,EAAE,GAAG,iBAAiB,CAAC,KAAK,mBAAmB;YACnD,mBAAmB,EAAE,YAAY,wBAAwB,MAAM;YAC/D,YAAY,EAAE,iBAAiB,CAAC,YAAY;SAC7C,CAAC,CAAC;QACH,IAAA,2BAAc,EAAC,IAAI,EAAE,WAAW,EAAE,OAAO,CAAC,OAAO,EAAE;YACjD,IAAI,EAAE,GAAG,iBAAiB,CAAC,KAAK,aAAa;YAC7C,mBAAmB,EAAE,YAAY,kBAAkB,MAAM;YACzD,YAAY,EAAE,iBAAiB,CAAC,YAAY;SAC7C,CAAC,CAAC;QACH,KAAK,IAAI,CAAC,GAAG,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;YAC3C,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;YACtB,IAAA,2BAAc,EAAC,IAAI,EAAE,WAAW,EAAE,OAAO,CAAC,OAAO,EAAE;gBACjD,IAAI,EAAE,GAAG,iBAAiB,CAAC,KAAK,QAAQ,IAAI,CAAC,GAAG,EAAE;gBAClD,mBAAmB,EAAE,YAAY,gBAAgB,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM;gBACjE,YAAY,EAAE,iBAAiB,CAAC,YAAY;aAC7C,CAAC,CAAC;QACL,CAAC;QAED,MAAM,IAAA,oBAAW,EAAC,IAAI,CAAC,CAAC;IAC1B,CAAC;CAAA"}
1
+ {"version":3,"file":"vue-intake-view.js","sourceRoot":"","sources":["../../../../../../packages/nx-adsp/src/generators/vue-intake-view/vue-intake-view.ts"],"names":[],"mappings":"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"}
@@ -107,6 +107,14 @@ describe('Vue Intake View Generator', () => {
107
107
  .toString();
108
108
  // Last step in the list moves to review, not another step.
109
109
  expect(step2).toContain('/applications/${nextId}/review');
110
+ // Each step tells the Stepper which step it is (1-based) -- the generator
111
+ // knows, so the view doesn't re-derive it from the route. Without it every
112
+ // page renders step one as current.
113
+ expect(step1).toContain(':step="1"');
114
+ expect(step2).toContain(':step="2"');
115
+ // Single-column forms ask for the narrow variant.
116
+ const routerForm = host.read('apps/test/src/router/index.ts').toString();
117
+ expect(routerForm).toContain("layout: 'form'");
110
118
  // A required field gets a validation block + requirement="required"; an
111
119
  // explicitly non-required one gets neither.
112
120
  expect(step2).toContain("found.push({ message: 'Email is required.'");
@@ -156,7 +164,7 @@ describe('Vue Intake View Generator', () => {
156
164
  "component: () => import('../views/ApplicationConfirmationView.vue')",
157
165
  );
158
166
  expect(
159
- routerTs.split('meta: { requiresAuth: true }').length - 1,
167
+ routerTs.split('requiresAuth: true').length - 1,
160
168
  ).toBe(4);
161
169
  // The existing route is untouched, not replaced.
162
170
  expect(routerTs).toContain("{ path: '/', component: HomeView }");
@@ -1,6 +1,13 @@
1
1
  <script setup lang="ts">
2
2
  import { ref, onMounted<% if (filterable) { %>, onUnmounted<% } %> } from 'vue';
3
- import { WorkspaceTable, formatCurrency, formatDate } from '<%= goaImportPath %>';
3
+ import {
4
+ WorkspaceTable,
5
+ <% if (filters.length) { -%>
6
+ FilterBar,
7
+ <% } -%>
8
+ formatCurrency,
9
+ formatDate,
10
+ } from '<%= goaImportPath %>';
4
11
  import { useApi } from '../composables/useApi';
5
12
 
6
13
  const { list } = useApi();
@@ -25,6 +32,38 @@ const search = ref('');
25
32
  let searchDebounce: ReturnType<typeof setTimeout> | undefined;
26
33
  <% } -%>
27
34
 
35
+ <% if (filters.length) { -%>
36
+ // A ref, not a const: a dropdown's options can be fetched (load them in `load`
37
+ // or a second call and assign into the matching descriptor) without changing
38
+ // FilterBar's contract.
39
+ const filterDescriptors = ref([
40
+ <% filters.forEach(function (filter) { -%>
41
+ {
42
+ key: '<%- filter.key %>',
43
+ label: '<%- filter.label %>',
44
+ type: '<%- filter.type %>' as const,
45
+ <% if (filter.type === 'dropdown') { -%>
46
+ options: <%- JSON.stringify(filter.options ?? []) %>,
47
+ <% if (filter.anyLabel) { -%>
48
+ anyLabel: '<%- filter.anyLabel %>',
49
+ <% } -%>
50
+ <% } -%>
51
+ },
52
+ <% }); -%>
53
+ ]);
54
+
55
+ // Query-ready values, keyed by filter. FilterBar owns the controls and the
56
+ // chips; the page reset and the reload below belong here because `page` is this
57
+ // view's state.
58
+ const filterValues = ref<Record<string, string>>({});
59
+
60
+ function onFiltersChange(next: Record<string, string>) {
61
+ filterValues.value = next;
62
+ page.value = 1;
63
+ void load();
64
+ }
65
+ <% } -%>
66
+
28
67
  const PAGE_SIZE = <%= pageSize %>;
29
68
 
30
69
  // load() fires from page change, sort<% if (filterable) { %>, and the debounced search<% } %>, so two requests can
@@ -48,6 +87,9 @@ async function load() {
48
87
  <% } -%>
49
88
  sortBy: sortBy.value,
50
89
  sortDir: sortDir.value,
90
+ <% if (filters.length) { -%>
91
+ filters: filterValues.value,
92
+ <% } -%>
51
93
  });
52
94
  if (sequence !== loadSequence) return;
53
95
  rows.value = result.rows;
@@ -118,6 +160,16 @@ onUnmounted(() => {
118
160
 
119
161
  <goa-spacer vspacing="l" />
120
162
  <% } -%>
163
+ <% if (filters.length) { -%>
164
+
165
+ <FilterBar
166
+ :filters="filterDescriptors"
167
+ :model-value="filterValues"
168
+ @update:model-value="onFiltersChange"
169
+ />
170
+
171
+ <goa-spacer vspacing="l" />
172
+ <% } -%>
121
173
 
122
174
  <WorkspaceTable
123
175
  :columns="columns"
@@ -5,6 +5,16 @@ export interface WorkspaceViewColumn {
5
5
  sortable?: boolean;
6
6
  }
7
7
 
8
+ export interface WorkspaceViewFilter {
9
+ key: string;
10
+ label: string;
11
+ type: 'dropdown' | 'date';
12
+ /** Dropdown only. Omit and populate `filterDescriptors` at runtime to fetch them. */
13
+ options?: { value: string; label: string }[];
14
+ /** Dropdown only. Label for the "no filter" choice. Defaults to "Any". */
15
+ anyLabel?: string;
16
+ }
17
+
8
18
  export interface Schema {
9
19
  project: string;
10
20
  name: string;
@@ -16,6 +26,8 @@ export interface Schema {
16
26
  * also accepted for programmatic callers (e.g. tests).
17
27
  */
18
28
  columns: string | WorkspaceViewColumn[];
29
+ /** Same JSON-string-on-the-CLI reasoning as `columns`. */
30
+ filters?: string | WorkspaceViewFilter[];
19
31
  detailRoute?: string;
20
32
  heading?: string;
21
33
  pageSize?: number;
@@ -23,11 +35,13 @@ export interface Schema {
23
35
  requiresAuth?: boolean;
24
36
  }
25
37
 
26
- export interface NormalizedSchema extends Omit<Schema, 'columns'> {
38
+ export interface NormalizedSchema extends Omit<Schema, 'columns' | 'filters'> {
27
39
  projectRoot: string;
28
40
  /** PascalCase view name with a "ListView" suffix, e.g. ApplicationsListView. */
29
41
  viewFileName: string;
30
42
  columns: WorkspaceViewColumn[];
43
+ /** Empty when --filters wasn't given, so the template can skip the FilterBar. */
44
+ filters: WorkspaceViewFilter[];
31
45
  heading: string;
32
46
  pageSize: number;
33
47
  filterable: boolean;
@@ -35,6 +35,10 @@
35
35
  "type": "string",
36
36
  "description": "JSON array of table columns, in display order -- e.g. '[{\"key\":\"status\",\"label\":\"Status\",\"type\":\"badge\",\"sortable\":true}]'. Each item: { key, label, type?: \"text\"|\"date\"|\"currency\"|\"badge\" (default \"text\"), sortable?: boolean }. A plain array is also accepted when this generator is invoked programmatically. Nx's CLI option coercion only supports comma-separated primitive lists for array-typed schema properties, not JSON -- a JSON string is the only CLI syntax that actually survives Nx's own arg parsing for a structured list like this."
37
37
  },
38
+ "filters": {
39
+ "type": "string",
40
+ "description": "JSON array of filter controls rendered above the table by FilterBar, beyond the --filterable search box -- e.g. '[{\"key\":\"status\",\"label\":\"Status\",\"type\":\"dropdown\",\"options\":[{\"value\":\"open\",\"label\":\"Open\"}]},{\"key\":\"from\",\"label\":\"From\",\"type\":\"date\"}]'. Each item: { key, label, type: \"dropdown\"|\"date\", options?: [{value,label}], anyLabel? }. Values are sent as query parameters alongside search/page/sort, so the API decides what each key means. Omit for no filter bar. Same JSON-string-on-the-CLI reasoning as --columns."
41
+ },
38
42
  "detailRoute": {
39
43
  "type": "string",
40
44
  "description": "If set, each row gets a \"View\" action linking to `${detailRoute}/${row.id}` -- typically a vue-detail-view's route with the :id segment dropped. Omit to leave the actions column for manual customization."
@@ -59,6 +63,12 @@
59
63
  "default": true
60
64
  }
61
65
  },
62
- "required": ["project", "name", "resource", "route", "columns"],
66
+ "required": [
67
+ "project",
68
+ "name",
69
+ "resource",
70
+ "route",
71
+ "columns"
72
+ ],
63
73
  "additionalProperties": false
64
74
  }
@@ -20,11 +20,26 @@ function parseColumns(columns) {
20
20
  }
21
21
  return parsed;
22
22
  }
23
+ // Same JSON-string-on-the-CLI reasoning as parseColumns above.
24
+ function parseFilters(filters) {
25
+ if (filters === undefined || filters === '')
26
+ return [];
27
+ const parsed = typeof filters === 'string' ? JSON.parse(filters) : filters;
28
+ if (!Array.isArray(parsed)) {
29
+ throw new Error('--filters must be a JSON array of { key, label, type: "dropdown"|"date", options?, anyLabel? } objects.');
30
+ }
31
+ for (const filter of parsed) {
32
+ if (filter.type !== 'dropdown' && filter.type !== 'date') {
33
+ throw new Error(`--filters[].type must be "dropdown" or "date"; got "${filter.type}" for "${filter.key}".`);
34
+ }
35
+ }
36
+ return parsed;
37
+ }
23
38
  function normalizeOptions(host, options) {
24
39
  var _a, _b, _c, _d, _e;
25
40
  const { root: projectRoot } = (0, devkit_1.readProjectConfiguration)(host, options.project);
26
41
  const className = (0, devkit_1.names)(options.name).className;
27
- return Object.assign(Object.assign({}, options), { projectRoot, columns: parseColumns(options.columns), viewFileName: `${className}ListView`,
42
+ return Object.assign(Object.assign({}, options), { projectRoot, columns: parseColumns(options.columns), filters: parseFilters(options.filters), viewFileName: `${className}ListView`,
28
43
  // className is PascalCase (e.g. "Applications") -- space it out for a
29
44
  // readable default heading ("Applications").
30
45
  heading: (_a = options.heading) !== null && _a !== void 0 ? _a : className.replace(/([A-Z])/g, ' $1').trim(), pageSize: (_b = options.pageSize) !== null && _b !== void 0 ? _b : 20, filterable: (_c = options.filterable) !== null && _c !== void 0 ? _c : true, requiresAuth: (_d = options.requiresAuth) !== null && _d !== void 0 ? _d : true,
@@ -44,6 +59,8 @@ function default_1(host, options) {
44
59
  path: normalizedOptions.route,
45
60
  componentImportPath: `../views/${normalizedOptions.viewFileName}.vue`,
46
61
  requiresAuth: normalizedOptions.requiresAuth,
62
+ // A multi-column table at the 1000px default wraps every cell.
63
+ layout: 'wide',
47
64
  });
48
65
  yield (0, devkit_1.formatFiles)(host);
49
66
  });
@@ -1 +1 @@
1
- {"version":3,"file":"vue-workspace-view.js","sourceRoot":"","sources":["../../../../../../packages/nx-adsp/src/generators/vue-workspace-view/vue-workspace-view.ts"],"names":[],"mappings":";;AAqDA,4BAyBC;;AA9ED,uCAMoB;AACpB,6BAA6B;AAC7B,uDAAwD;AACxD,qEAE0C;AAG1C,6EAA6E;AAC7E,4EAA4E;AAC5E,6EAA6E;AAC7E,6EAA6E;AAC7E,0EAA0E;AAC1E,8EAA8E;AAC9E,iEAAiE;AACjE,SAAS,YAAY,CAAC,OAA0B;IAC9C,MAAM,MAAM,GAAG,OAAO,OAAO,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC;IAC3E,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAClD,MAAM,IAAI,KAAK,CACb,uFAAuF,CACxF,CAAC;IACJ,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,SAAS,gBAAgB,CAAC,IAAU,EAAE,OAAe;;IACnD,MAAM,EAAE,IAAI,EAAE,WAAW,EAAE,GAAG,IAAA,iCAAwB,EAAC,IAAI,EAAE,OAAO,CAAC,OAAO,CAAC,CAAC;IAE9E,MAAM,SAAS,GAAG,IAAA,cAAK,EAAC,OAAO,CAAC,IAAI,CAAC,CAAC,SAAS,CAAC;IAChD,uCACK,OAAO,KACV,WAAW,EACX,OAAO,EAAE,YAAY,CAAC,OAAO,CAAC,OAAO,CAAC,EACtC,YAAY,EAAE,GAAG,SAAS,UAAU;QACpC,sEAAsE;QACtE,6CAA6C;QAC7C,OAAO,EAAE,MAAA,OAAO,CAAC,OAAO,mCAAI,SAAS,CAAC,OAAO,CAAC,UAAU,EAAE,KAAK,CAAC,CAAC,IAAI,EAAE,EACvE,QAAQ,EAAE,MAAA,OAAO,CAAC,QAAQ,mCAAI,EAAE,EAChC,UAAU,EAAE,MAAA,OAAO,CAAC,UAAU,mCAAI,IAAI,EACtC,YAAY,EAAE,MAAA,OAAO,CAAC,YAAY,mCAAI,IAAI;QAC1C,6EAA6E;QAC7E,2EAA2E;QAC3E,4EAA4E;QAC5E,WAAW,EAAE,MAAA,OAAO,CAAC,WAAW,mCAAI,IAAI,IACxC;AACJ,CAAC;AAED,mBAA+B,IAAU,EAAE,OAAe;;QACxD,MAAM,iBAAiB,GAAG,gBAAgB,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;QAE1D,2EAA2E;QAC3E,oCAAoC;QACpC,MAAM,IAAA,wBAAsB,EAAC,IAAI,CAAC,CAAC;QAEnC,IAAA,sBAAa,EACX,IAAI,EACJ,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,OAAO,CAAC,EAC7B,iBAAiB,CAAC,WAAW,kCAExB,iBAAiB,KACpB,aAAa,EAAE,IAAA,wCAAuB,EAAC,IAAI,CAAC,EAC5C,IAAI,EAAE,EAAE,IAEX,CAAC;QAEF,IAAA,2BAAc,EAAC,IAAI,EAAE,iBAAiB,CAAC,WAAW,EAAE,iBAAiB,CAAC,OAAO,EAAE;YAC7E,IAAI,EAAE,iBAAiB,CAAC,KAAK;YAC7B,mBAAmB,EAAE,YAAY,iBAAiB,CAAC,YAAY,MAAM;YACrE,YAAY,EAAE,iBAAiB,CAAC,YAAY;SAC7C,CAAC,CAAC;QAEH,MAAM,IAAA,oBAAW,EAAC,IAAI,CAAC,CAAC;IAC1B,CAAC;CAAA"}
1
+ {"version":3,"file":"vue-workspace-view.js","sourceRoot":"","sources":["../../../../../../packages/nx-adsp/src/generators/vue-workspace-view/vue-workspace-view.ts"],"names":[],"mappings":"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"}
@@ -149,6 +149,71 @@ describe('Vue Workspace View Generator', () => {
149
149
  expect(view).toContain('searchDebounce');
150
150
  }, 30000);
151
151
 
152
+ describe('--filters', () => {
153
+ const filters = JSON.stringify([
154
+ {
155
+ key: 'status',
156
+ label: 'Status',
157
+ type: 'dropdown',
158
+ options: [{ value: 'open', label: 'Open' }],
159
+ anyLabel: 'All statuses',
160
+ },
161
+ { key: 'from', label: 'From', type: 'date' },
162
+ ]);
163
+
164
+ it('wires the shared FilterBar and sends the values as query filters', async () => {
165
+ await generator(host, { ...baseOptions, filters });
166
+ const view = host
167
+ .read('apps/test/src/views/ApplicationsListView.vue')
168
+ .toString();
169
+
170
+ expect(view).toContain('FilterBar');
171
+ expect(view).toContain(':filters="filterDescriptors"');
172
+ expect(view).toContain('@update:model-value="onFiltersChange"');
173
+ // The values object goes straight through useApi's adapter.
174
+ expect(view).toContain('filters: filterValues.value');
175
+ // The view owns the page reset, not FilterBar -- it owns `page`.
176
+ expect(view).toContain('page.value = 1');
177
+ // Descriptors are a ref so fetched dropdown options can be assigned in.
178
+ expect(view).toContain('const filterDescriptors = ref([');
179
+ expect(view).toContain("anyLabel: 'All statuses'");
180
+ expect(view).toContain("type: 'date' as const");
181
+ });
182
+
183
+ it('leaves output byte-identical when --filters is omitted', async () => {
184
+ await generator(host, baseOptions);
185
+ const without = host
186
+ .read('apps/test/src/views/ApplicationsListView.vue')
187
+ .toString();
188
+ expect(without).not.toContain('FilterBar');
189
+ expect(without).not.toContain('filterValues');
190
+ expect(without).not.toContain('onFiltersChange');
191
+ });
192
+
193
+ it('rejects a filter type it cannot render, naming the offender', async () => {
194
+ await expect(
195
+ generator(host, {
196
+ ...baseOptions,
197
+ filters: JSON.stringify([
198
+ { key: 'x', label: 'X', type: 'checkbox' },
199
+ ]),
200
+ }),
201
+ ).rejects.toThrow('--filters[].type must be "dropdown" or "date"; got "checkbox" for "x".');
202
+ });
203
+
204
+ it('accepts a real array, the form a programmatic caller passes', async () => {
205
+ await generator(host, {
206
+ ...baseOptions,
207
+ filters: [{ key: 'status', label: 'Status', type: 'dropdown' }],
208
+ });
209
+ const view = host
210
+ .read('apps/test/src/views/ApplicationsListView.vue')
211
+ .toString();
212
+ expect(view).toContain('FilterBar');
213
+ expect(view).toContain('options: []');
214
+ });
215
+ });
216
+
152
217
  it('omits onUnmounted along with the debounce when --filterable=false', async () => {
153
218
  // onUnmounted exists only to clear the search debounce, so importing it
154
219
  // unconditionally would be an unused import and fail the generated lint.
@@ -206,7 +271,9 @@ describe('Vue Workspace View Generator', () => {
206
271
  expect(routerTs).toContain(
207
272
  "component: () => import('../views/ApplicationsListView.vue')",
208
273
  );
209
- expect(routerTs).toContain('meta: { requiresAuth: true }');
274
+ expect(routerTs).toContain('requiresAuth: true');
275
+ // A data table asks for the wide variant -- the generator knows it emitted one.
276
+ expect(routerTs).toContain("layout: 'wide'");
210
277
  expect(routerTs).toContain("{ path: '/', component: HomeView }");
211
278
  }, 30000);
212
279
 
@@ -4,5 +4,12 @@ export interface VueRouteSpec {
4
4
  /** Relative import path passed to () => import(...), e.g. '../views/FooView.vue'. */
5
5
  componentImportPath: string;
6
6
  requiresAuth?: boolean;
7
+ /**
8
+ * Content width for the view, read by App.vue's `contentWidth` and passed to
9
+ * AppLayout. Omit for the 1000px default; a generator emitting a data table
10
+ * should say 'wide' and one emitting a single-column form 'form', since it
11
+ * already knows which it produced.
12
+ */
13
+ layout?: 'wide' | 'form';
7
14
  }
8
15
  export declare function insertVueRoute(host: Tree, projectRoot: string, project: string, route: VueRouteSpec): void;
@@ -16,8 +16,12 @@ function insertVueRoute(host, projectRoot, project, route) {
16
16
  if (markerIndex === -1) {
17
17
  throw new Error(`Could not find "${marker}" in ${routerPath} -- has its shape changed since @abgov/nx-adsp:vue-app generated it?`);
18
18
  }
19
- const metaLine = route.requiresAuth
20
- ? `\n meta: { requiresAuth: true },`
19
+ const metaEntries = [
20
+ ...(route.requiresAuth ? ['requiresAuth: true'] : []),
21
+ ...(route.layout ? [`layout: '${route.layout}'`] : []),
22
+ ];
23
+ const metaLine = metaEntries.length
24
+ ? `\n meta: { ${metaEntries.join(', ')} },`
21
25
  : '';
22
26
  const routeSnippet = `{\n` +
23
27
  ` path: '${route.path}',\n` +
@@ -1 +1 @@
1
- {"version":3,"file":"vue-router.js","sourceRoot":"","sources":["../../../../../packages/nx-adsp/src/utils/vue-router.ts"],"names":[],"mappings":";;AAYA,wCAqCC;AAxCD,6EAA6E;AAC7E,wEAAwE;AACxE,6EAA6E;AAC7E,SAAgB,cAAc,CAC5B,IAAU,EACV,WAAmB,EACnB,OAAe,EACf,KAAmB;;IAEnB,MAAM,UAAU,GAAG,GAAG,WAAW,sBAAsB,CAAC;IACxD,MAAM,OAAO,GAAG,MAAA,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,0CAAE,QAAQ,EAAE,CAAC;IAClD,IAAI,OAAO,KAAK,SAAS,EAAE,CAAC;QAC1B,MAAM,IAAI,KAAK,CACb,kBAAkB,UAAU,WAAW,OAAO,0DAA0D,CACzG,CAAC;IACJ,CAAC;IAED,MAAM,MAAM,GAAG,WAAW,CAAC;IAC3B,MAAM,WAAW,GAAG,OAAO,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;IAC5C,IAAI,WAAW,KAAK,CAAC,CAAC,EAAE,CAAC;QACvB,MAAM,IAAI,KAAK,CACb,mBAAmB,MAAM,QAAQ,UAAU,sEAAsE,CAClH,CAAC;IACJ,CAAC;IAED,MAAM,QAAQ,GAAG,KAAK,CAAC,YAAY;QACjC,CAAC,CAAC,uCAAuC;QACzC,CAAC,CAAC,EAAE,CAAC;IACP,MAAM,YAAY,GAChB,KAAK;QACL,gBAAgB,KAAK,CAAC,IAAI,MAAM;QAChC,kCAAkC,KAAK,CAAC,mBAAmB,MAAM,QAAQ,IAAI;QAC7E,QAAQ,CAAC;IAEX,MAAM,QAAQ,GAAG,WAAW,GAAG,MAAM,CAAC,MAAM,CAAC;IAC7C,MAAM,OAAO,GACX,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,QAAQ,CAAC;QAC1B,SAAS,YAAY,EAAE;QACvB,OAAO,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;IAC1B,IAAI,CAAC,KAAK,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC;AAClC,CAAC"}
1
+ {"version":3,"file":"vue-router.js","sourceRoot":"","sources":["../../../../../packages/nx-adsp/src/utils/vue-router.ts"],"names":[],"mappings":";;AAmBA,wCAyCC;AA5CD,6EAA6E;AAC7E,wEAAwE;AACxE,6EAA6E;AAC7E,SAAgB,cAAc,CAC5B,IAAU,EACV,WAAmB,EACnB,OAAe,EACf,KAAmB;;IAEnB,MAAM,UAAU,GAAG,GAAG,WAAW,sBAAsB,CAAC;IACxD,MAAM,OAAO,GAAG,MAAA,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,0CAAE,QAAQ,EAAE,CAAC;IAClD,IAAI,OAAO,KAAK,SAAS,EAAE,CAAC;QAC1B,MAAM,IAAI,KAAK,CACb,kBAAkB,UAAU,WAAW,OAAO,0DAA0D,CACzG,CAAC;IACJ,CAAC;IAED,MAAM,MAAM,GAAG,WAAW,CAAC;IAC3B,MAAM,WAAW,GAAG,OAAO,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;IAC5C,IAAI,WAAW,KAAK,CAAC,CAAC,EAAE,CAAC;QACvB,MAAM,IAAI,KAAK,CACb,mBAAmB,MAAM,QAAQ,UAAU,sEAAsE,CAClH,CAAC;IACJ,CAAC;IAED,MAAM,WAAW,GAAG;QAClB,GAAG,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,oBAAoB,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;QACrD,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,YAAY,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;KACvD,CAAC;IACF,MAAM,QAAQ,GAAG,WAAW,CAAC,MAAM;QACjC,CAAC,CAAC,mBAAmB,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK;QAChD,CAAC,CAAC,EAAE,CAAC;IACP,MAAM,YAAY,GAChB,KAAK;QACL,gBAAgB,KAAK,CAAC,IAAI,MAAM;QAChC,kCAAkC,KAAK,CAAC,mBAAmB,MAAM,QAAQ,IAAI;QAC7E,QAAQ,CAAC;IAEX,MAAM,QAAQ,GAAG,WAAW,GAAG,MAAM,CAAC,MAAM,CAAC;IAC7C,MAAM,OAAO,GACX,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,QAAQ,CAAC;QAC1B,SAAS,YAAY,EAAE;QACvB,OAAO,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;IAC1B,IAAI,CAAC,KAAK,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC;AAClC,CAAC"}