@signal24/vue-foundation 4.2.1 → 4.2.4

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,7 +1,7 @@
1
1
  {
2
2
  "name": "@signal24/vue-foundation",
3
3
  "type": "module",
4
- "version": "4.2.1",
4
+ "version": "4.2.4",
5
5
  "description": "Common components, directives, and helpers for Vue 3 apps",
6
6
  "module": "./dist/vue-foundation.es.js",
7
7
  "bin": {
@@ -39,15 +39,17 @@
39
39
  "date-fns": "^2.30.0",
40
40
  "lodash": "^4.17.21",
41
41
  "type-fest": "^3.11.0",
42
+ "uuid": "^9.0.0",
42
43
  "vue": "^3.3.4"
43
44
  },
44
45
  "devDependencies": {
45
46
  "@nabla/vite-plugin-eslint": "^1.5.0",
46
- "@rushstack/eslint-patch": "^1.2.0",
47
+ "@rushstack/eslint-patch": "^1.3.0",
47
48
  "@tsconfig/node18": "^2.0.1",
48
49
  "@types/jsdom": "^21.1.1",
49
50
  "@types/lodash": "^4.14.194",
50
51
  "@types/node": "^18.15.11",
52
+ "@types/uuid": "^9.0.1",
51
53
  "@vitejs/plugin-vue": "^4.2.3",
52
54
  "@vue/eslint-config-prettier": "^7.1.0",
53
55
  "@vue/eslint-config-typescript": "^11.0.3",
@@ -58,7 +60,7 @@
58
60
  "eslint-plugin-cypress": "^2.13.3",
59
61
  "eslint-plugin-simple-import-sort": "^10.0.0",
60
62
  "eslint-plugin-unused-imports": "^2.0.0",
61
- "eslint-plugin-vue": "^9.13.0",
63
+ "eslint-plugin-vue": "^9.14.0",
62
64
  "jsdom": "^22.0.0",
63
65
  "openapi-typescript-codegen": "^0.24.0",
64
66
  "prettier": "^2.8.8",
@@ -1,6 +1,6 @@
1
1
  <template>
2
2
  <Modal class="vf-alert" :class="classes">
3
- <template v-if="!isBare" v-slot:header>
3
+ <template v-if="!isBare && title" v-slot:header>
4
4
  <h1>{{ title }}</h1>
5
5
  </template>
6
6
 
@@ -1,5 +1,5 @@
1
1
  <template>
2
- <VfSmartSelect v-model="selectedItem" :options="options" :formatter="ezFormatter" :null-title="nullTitle" />
2
+ <VfSmartSelect v-model="selectedItem" :options="computedOpts" :formatter="ezFormatter" :null-title="nullTitle" />
3
3
  </template>
4
4
 
5
5
  <script lang="ts" setup>
@@ -18,7 +18,7 @@ const props = defineProps<{
18
18
  formatter?: (value: any) => string;
19
19
  }>();
20
20
 
21
- const options = computed(() => {
21
+ const computedOpts = computed(() => {
22
22
  return Array.isArray(props.options)
23
23
  ? props.options.map(o => ({ value: o, label: o }))
24
24
  : Object.entries(props.options).map(([value, label]) => ({
@@ -38,14 +38,14 @@ const emit = defineEmits<{
38
38
  (e: 'update:modelValue', value: string | null): void;
39
39
  }>();
40
40
 
41
- const selectedItem = ref<GenericObject | null>(options.value.find(o => o.value === props.modelValue) ?? null);
41
+ const selectedItem = ref<GenericObject | null>(computedOpts.value.find(o => o.value === props.modelValue) ?? null);
42
42
  watch(
43
43
  () => props.modelValue,
44
44
  value => {
45
- selectedItem.value = options.value.find(o => o.value === value) ?? null;
45
+ selectedItem.value = computedOpts.value.find(o => o.value === value) ?? null;
46
46
  }
47
47
  );
48
48
  watch(selectedItem, value => {
49
- emit('update:modelValue', value ? options.value.find(o => isEqual(o, value))?.value ?? null : null);
49
+ emit('update:modelValue', value ? computedOpts.value.find(o => isEqual(o, value))?.value ?? null : null);
50
50
  });
51
51
  </script>
@@ -1,9 +1,23 @@
1
1
  import type { DirectiveBinding, ObjectDirective } from 'vue';
2
2
 
3
- import { type IInfiniteScrollOptions, useInfiniteScroll } from '../hooks/infinite-scroll';
3
+ import { InfiniteScrollHandler } from '../hooks/infinite-scroll';
4
4
 
5
- export const vInfiniteScroll: ObjectDirective<Element, IInfiniteScrollOptions> = {
6
- created(_el: Element, binding: DirectiveBinding<IInfiniteScrollOptions>) {
7
- useInfiniteScroll(binding.value, binding.instance!.$);
5
+ const HandlerSymbol = Symbol('InfiniteScrollHandler');
6
+ type InfiniteScrollElement = Element & { [HandlerSymbol]?: InfiniteScrollHandler };
7
+ type InfiniteScrollBindingValue = () => void;
8
+
9
+ export const vInfiniteScroll: ObjectDirective<Element, InfiniteScrollBindingValue> = {
10
+ mounted(el: InfiniteScrollElement, binding: DirectiveBinding<InfiniteScrollBindingValue>) {
11
+ el[HandlerSymbol] = new InfiniteScrollHandler(el, binding.value);
12
+ },
13
+
14
+ updated(el: InfiniteScrollElement, binding: DirectiveBinding<InfiniteScrollBindingValue>) {
15
+ el[HandlerSymbol]?.uninstall();
16
+ el[HandlerSymbol] = new InfiniteScrollHandler(el, binding.value);
17
+ },
18
+
19
+ unmounted(el: InfiniteScrollElement) {
20
+ el[HandlerSymbol]?.uninstall();
21
+ delete el[HandlerSymbol];
8
22
  }
9
23
  };
@@ -18,17 +18,17 @@ interface IBaseHttpRequest {
18
18
  request<T>(options: IRequestOptions): ICancelablePromise<T>;
19
19
  }
20
20
 
21
- interface IApiClient {
21
+ export interface IApiClient {
22
22
  request: IBaseHttpRequest;
23
23
  }
24
24
 
25
- interface IApiError extends Error {
25
+ export interface IApiError extends Error {
26
26
  status: number;
27
27
  statusText: string;
28
28
  body: any;
29
29
  }
30
30
 
31
- declare class ICancelablePromise<T = any> {
31
+ export declare class ICancelablePromise<T = any> {
32
32
  constructor(executor: (resolve: (value: any) => void, reject: (reason: any) => void, onCancel: (cancel: () => void) => void) => void);
33
33
  then<TResult1 = any, TResult2 = never>(
34
34
  onfulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | undefined | null,
@@ -46,7 +46,7 @@ interface IWrappedApiClientOptions<P extends ICancelablePromise = ICancelablePro
46
46
  CancelablePromise: new (...arguments_: Arguments) => P;
47
47
  }
48
48
 
49
- function isApiError(err: any): err is IApiError {
49
+ export function isApiError(err: any): err is IApiError {
50
50
  return err instanceof Error && 'status' in err && 'body' in err;
51
51
  }
52
52
 
@@ -1,3 +1,5 @@
1
+ import { v4 as uuidv4 } from 'uuid';
2
+
1
3
  // placing this here so we don't have to use the ESLint rule everywhere
2
4
  // eslint-disable-next-line vue/prefer-import-from-vue
3
5
  export { escapeHtml } from '@vue/shared';
@@ -25,3 +27,7 @@ export function formatUSCurrency(value: string | number) {
25
27
  .replace(/\B(?=(\d{3})+(?!\d))/g, ',')
26
28
  );
27
29
  }
30
+
31
+ export function uuid() {
32
+ return uuidv4();
33
+ }
@@ -27,22 +27,19 @@ export function installScrollHook(cmp: InfiniteScrollComponent, options: IInfini
27
27
 
28
28
  if (options.elScrolledToBottom) {
29
29
  hookState.el = new InfiniteScrollHandler(cmp.vnode.el as Element, options.elScrolledToBottom);
30
- hookState.el.install();
31
30
  }
32
31
 
33
32
  if (options.ancestorScrolledToBottom) {
34
33
  const scrollableAncestorEl = discoverScrollableAncestorEl(cmp.vnode.el as Element);
35
34
  if (scrollableAncestorEl) {
36
35
  hookState.ancestor = new InfiniteScrollHandler(scrollableAncestorEl, options.ancestorScrolledToBottom);
37
- hookState.ancestor.install();
38
36
  } else {
39
- console.warn('no scollable ancestor found for component:', cmp);
37
+ console.warn('[VueFoundation] No scollable ancestor found for component:', cmp);
40
38
  }
41
39
  }
42
40
 
43
41
  if (options.windowScrolledToBottom) {
44
42
  hookState.window = new InfiniteScrollHandler(window as unknown as Element, options.windowScrolledToBottom);
45
- hookState.window.install();
46
43
  }
47
44
 
48
45
  cmp[HookState] = hookState;
@@ -83,7 +80,9 @@ function discoverScrollableAncestorEl(el: Element): Element | null {
83
80
  export class InfiniteScrollHandler {
84
81
  isTripped = false;
85
82
 
86
- constructor(private el: Element, private handler: (e: Event) => void) {}
83
+ constructor(private el: Element, private handler: (e: Event) => void) {
84
+ this.install();
85
+ }
87
86
 
88
87
  install() {
89
88
  this.el.addEventListener('scroll', this.onScrollWithContext);
@@ -2,14 +2,15 @@
2
2
 
3
3
  import { existsSync } from 'fs';
4
4
 
5
- import { generateOpenapiClient } from './vite-openapi-plugin.js';
5
+ import { generateOpenapiClient, loadOpenapiOverrides } from './vite-openapi-plugin.js';
6
6
 
7
7
  if (!process.argv[2]) {
8
- throw new Error('Usage: vf-generate-openapi-client <openapi-yaml-path>');
8
+ throw new Error('Usage: vf-generate-openapi-client <openapi-yaml-path> [<openapi-output-path>]');
9
9
  }
10
10
 
11
11
  if (!existsSync(process.argv[2])) {
12
12
  throw new Error(`OpenAPI YAML file not found: ${process.argv[2]}`);
13
13
  }
14
14
 
15
- await generateOpenapiClient(process.argv[2]);
15
+ loadOpenapiOverrides();
16
+ await generateOpenapiClient(process.argv[2], process.argv[3]);
@@ -1,12 +1,38 @@
1
1
  import { createHash } from 'node:crypto';
2
- import { existsSync, readFileSync, watch } from 'node:fs';
2
+ import { copyFileSync, existsSync, readFileSync, watch } from 'node:fs';
3
3
  import { rm } from 'node:fs/promises';
4
4
 
5
5
  import * as OpenAPI from 'openapi-typescript-codegen';
6
6
 
7
+ const DEFAULT_OUT_PATH = './src/openapi-client-generated';
8
+
7
9
  let generatedHash: string | null = null;
10
+ let overridesMap: Record<string, string> | null = null;
11
+ let overridesInverseMap: Record<string, string> | null = null;
12
+
13
+ export function loadOpenapiOverrides() {
14
+ if (!existsSync('./openapi-specs.dev.json')) {
15
+ return;
16
+ }
8
17
 
9
- export function openapiClientGeneratorPlugin(openapiYamlPath: string) {
18
+ try {
19
+ const overridesContent = readFileSync('./openapi-specs.dev.json', 'utf8');
20
+ overridesMap = JSON.parse(overridesContent);
21
+ overridesInverseMap = Object.fromEntries(Object.entries(overridesMap!).map(([k, v]) => [v, k]));
22
+ } catch (e) {
23
+ console.error('Failed to load openapi-specs.dev.json:', e);
24
+ }
25
+ }
26
+
27
+ export function openapiClientGeneratorPlugin(
28
+ openapiYamlPath: string,
29
+ outPath: string = DEFAULT_OUT_PATH
30
+ ): {
31
+ name: string;
32
+ apply: 'serve';
33
+ buildStart(): void;
34
+ closeBundle(): void;
35
+ } {
10
36
  let generator: ReturnType<typeof getGenerator> = null;
11
37
 
12
38
  return {
@@ -14,7 +40,11 @@ export function openapiClientGeneratorPlugin(openapiYamlPath: string) {
14
40
  apply: 'serve',
15
41
 
16
42
  buildStart() {
17
- generator = getGenerator(openapiYamlPath);
43
+ // apply a slight delay so any output doesn't get pushed off screen
44
+ setTimeout(() => {
45
+ loadOpenapiOverrides();
46
+ generator = getGenerator(openapiYamlPath, outPath);
47
+ }, 250);
18
48
  },
19
49
 
20
50
  closeBundle() {
@@ -23,19 +53,21 @@ export function openapiClientGeneratorPlugin(openapiYamlPath: string) {
23
53
  };
24
54
  }
25
55
 
26
- function getGenerator(openapiYamlPath: string) {
27
- if (!existsSync(openapiYamlPath)) {
28
- console.log(`OpenAPI YAML file not found: ${openapiYamlPath}`);
56
+ function getGenerator(openapiYamlPath: string, outPath: string) {
57
+ const resolvedPath = overridesMap?.[openapiYamlPath] ?? openapiYamlPath;
58
+
59
+ if (!existsSync(resolvedPath)) {
60
+ console.log(`OpenAPI YAML file not found: ${resolvedPath}`);
29
61
  return null;
30
62
  }
31
63
 
32
- const watcher = watch(openapiYamlPath);
64
+ const watcher = watch(resolvedPath);
33
65
  watcher.on('change', () => {
34
66
  // give the writes a moment to settle
35
- setTimeout(() => generateOpenapiClient(openapiYamlPath), 100);
67
+ setTimeout(() => generateOpenapiClient(resolvedPath, outPath), 100);
36
68
  });
37
69
 
38
- generateOpenapiClient(openapiYamlPath);
70
+ generateOpenapiClient(resolvedPath, outPath);
39
71
 
40
72
  return {
41
73
  close() {
@@ -44,7 +76,7 @@ function getGenerator(openapiYamlPath: string) {
44
76
  };
45
77
  }
46
78
 
47
- export async function generateOpenapiClient(openapiYamlPath: string) {
79
+ async function generateOpenapiClientInternal(openapiYamlPath: string, outPath: string = DEFAULT_OUT_PATH) {
48
80
  const yaml = readFileSync(openapiYamlPath, 'utf8');
49
81
  const hash = createHash('sha256').update(yaml).digest('hex');
50
82
 
@@ -56,21 +88,30 @@ export async function generateOpenapiClient(openapiYamlPath: string) {
56
88
 
57
89
  try {
58
90
  try {
59
- await rm('./src/openapi-client-generated', { recursive: true });
91
+ await rm(outPath, { recursive: true });
60
92
  } catch (e) {
61
93
  // ignore
62
94
  }
63
95
 
64
96
  await OpenAPI.generate({
65
97
  input: openapiYamlPath,
66
- output: './src/openapi-client-generated',
98
+ output: outPath,
67
99
  clientName: 'ApiClient',
68
100
  useOptions: true,
69
101
  useUnionTypes: true
70
102
  });
71
103
 
72
- console.log(`[${new Date().toISOString()}] Generated client from ${openapiYamlPath} to ./src/openapi-client/`);
104
+ if (overridesInverseMap?.[openapiYamlPath]) {
105
+ copyFileSync(openapiYamlPath, overridesInverseMap[openapiYamlPath]);
106
+ }
107
+
108
+ console.log(`[${new Date().toISOString()}] Generated client from ${openapiYamlPath} to ${outPath}/`);
73
109
  } catch (err) {
74
110
  console.error(`[${new Date().toISOString()}] Error generating client from ${openapiYamlPath}:`, err);
75
111
  }
76
112
  }
113
+
114
+ export async function generateOpenapiClient(openapiYamlPath: string, outPath: string = DEFAULT_OUT_PATH) {
115
+ const resolvedPath = overridesMap?.[openapiYamlPath] ?? openapiYamlPath;
116
+ return generateOpenapiClientInternal(resolvedPath, outPath);
117
+ }
@@ -27,7 +27,7 @@ export default defineConfig({
27
27
  output: {
28
28
  assetFileNames: assetInfo => {
29
29
  if (assetInfo.name === 'index.css') return 'vue-foundation.css';
30
- return assetInfo.name;
30
+ return assetInfo.name!;
31
31
  },
32
32
  exports: 'named',
33
33
  globals: {
File without changes