@signal24/vue-foundation 4.16.0 → 4.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/demo/components/demo-root.vue +21 -0
  2. package/demo/components/demo-vf-smart-select.vue +28 -0
  3. package/demo/index.html +14 -0
  4. package/demo/index.ts +10 -0
  5. package/demo/vite.config.ts +23 -0
  6. package/dist/demo/components/demo-root.vue.d.ts +2 -0
  7. package/dist/demo/components/demo-vf-smart-select.vue.d.ts +2 -0
  8. package/dist/demo/index.d.ts +1 -0
  9. package/dist/demo/vite.config.d.ts +2 -0
  10. package/dist/src/components/index.d.ts +5 -5
  11. package/dist/src/components/overlay-anchor.vue.d.ts +1 -1
  12. package/dist/src/components/overlay-container.d.ts +1 -1
  13. package/dist/src/components/toast-helpers.d.ts +1 -1
  14. package/dist/src/components/vf-ajax-select.vue.d.ts +26 -0
  15. package/dist/src/components/{alert-modal.vue.d.ts → vf-alert-modal.vue.d.ts} +1 -1
  16. package/dist/src/components/{ez-smart-select.vue.d.ts → vf-ez-smart-select.vue.d.ts} +13 -5
  17. package/dist/src/components/{modal.vue.d.ts → vf-modal.vue.d.ts} +1 -1
  18. package/dist/src/components/vf-smart-select.vue.d.ts +47 -0
  19. package/dist/src/components/{toast.vue.d.ts → vf-toast.vue.d.ts} +1 -1
  20. package/dist/vue-foundation.es.js +828 -893
  21. package/eslint.config.mjs +67 -0
  22. package/package.json +10 -8
  23. package/src/components/alert-helpers.ts +1 -1
  24. package/src/components/index.ts +5 -5
  25. package/src/components/overlay-container.ts +5 -1
  26. package/src/components/toast-helpers.ts +1 -1
  27. package/src/components/vf-ajax-select.vue +61 -0
  28. package/src/components/{alert-modal.vue → vf-alert-modal.vue} +5 -5
  29. package/src/components/{ez-smart-select.vue → vf-ez-smart-select.vue} +12 -8
  30. package/src/components/{modal.vue → vf-modal.vue} +3 -3
  31. package/src/components/vf-smart-select.vue +585 -0
  32. package/src/directives/duration.ts +3 -3
  33. package/src/directives/hotkey.ts +1 -0
  34. package/src/filters/index.ts +1 -0
  35. package/src/helpers/array.ts +1 -0
  36. package/src/helpers/error.ts +4 -0
  37. package/src/helpers/object.ts +1 -0
  38. package/tsconfig.app.json +1 -1
  39. package/tsconfig.node.json +1 -1
  40. package/tsconfig.vitest.json +2 -2
  41. package/.eslintrc.cjs +0 -35
  42. package/dist/src/components/ajax-select.vue.d.ts +0 -19
  43. package/dist/src/components/smart-select.vue.d.ts +0 -115
  44. package/src/components/ajax-select.vue +0 -75
  45. package/src/components/smart-select.vue +0 -609
  46. /package/src/components/{toast.vue → vf-toast.vue} +0 -0
@@ -0,0 +1,67 @@
1
+ import eslint from '@eslint/js';
2
+ import eslintPluginPrettierRecommended from 'eslint-plugin-prettier/recommended';
3
+ import simpleImportSort from 'eslint-plugin-simple-import-sort';
4
+ import unusedImports from 'eslint-plugin-unused-imports';
5
+ import pluginVue from 'eslint-plugin-vue';
6
+ import tseslint from 'typescript-eslint';
7
+
8
+ export default tseslint.config(
9
+ // global ignores
10
+ {
11
+ ignores: ['dist/', '.yarn/']
12
+ },
13
+
14
+ // presets
15
+ eslint.configs.recommended,
16
+ ...tseslint.configs.recommended,
17
+ ...pluginVue.configs['flat/recommended'],
18
+
19
+ // register parser options
20
+ {
21
+ files: ['**/*.ts', '**/*.tsx', '**/*.vue'],
22
+ languageOptions: {
23
+ parserOptions: {
24
+ parser: tseslint.parser,
25
+ extraFileExtensions: ['.vue'],
26
+ projectService: true
27
+ },
28
+
29
+ // not sure why some of these TS generics are needed.
30
+ // will circle back when Vue officially updates to ESLint 9
31
+ globals: {
32
+ Omit: false,
33
+ Record: false
34
+ }
35
+ }
36
+ },
37
+
38
+ // plugins & configs
39
+ eslintPluginPrettierRecommended,
40
+ {
41
+ plugins: {
42
+ 'unused-imports': unusedImports,
43
+ 'simple-import-sort': simpleImportSort
44
+ },
45
+
46
+ rules: {
47
+ 'no-console': 'off',
48
+ 'no-debugger': 'off',
49
+ 'simple-import-sort/imports': 'error',
50
+ 'simple-import-sort/exports': 'error',
51
+ 'prettier/prettier': 'warn',
52
+ 'no-unused-vars': 'off',
53
+ 'unused-imports/no-unused-imports': 'error',
54
+ 'unused-imports/no-unused-vars': [
55
+ 'warn',
56
+ {
57
+ vars: 'all',
58
+ varsIgnorePattern: '^_',
59
+ args: 'all',
60
+ argsIgnorePattern: '^_'
61
+ }
62
+ ]
63
+ // 'vue/multi-word-component-names': 'off',
64
+ // 'vue/no-reserved-component-names': 'off'
65
+ }
66
+ }
67
+ );
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@signal24/vue-foundation",
3
3
  "type": "module",
4
- "version": "4.16.0",
4
+ "version": "4.17.0",
5
5
  "description": "Common components, directives, and helpers for Vue 3 apps",
6
6
  "module": "./dist/vue-foundation.es.js",
7
7
  "exports": {
@@ -23,13 +23,13 @@
23
23
  "dev": "vite",
24
24
  "build": "rm -rf dist && vite build && vue-tsc -p tsconfig.app.json && tsc -p tsconfig.vite-plugins.json && find dist -name '*.tsbuildinfo' -delete",
25
25
  "build:watch": "fswatch -o src | xargs -n1 -I{} yarn build",
26
- "preview": "vite preview",
26
+ "demo": "vite -c ./demo/vite.config.ts ./demo",
27
27
  "test:types": "vue-tsc -p tsconfig.vitest.json",
28
28
  "test:unit": "vitest",
29
29
  "test:e2e": "start-server-and-test preview http://localhost:4173 'cypress run --e2e'",
30
30
  "test:e2e:dev": "start-server-and-test 'vite dev --port 4173' http://localhost:4173 'cypress open --e2e'",
31
31
  "test:e2e:dev:remote": "DISPLAY=:0 yarn test:e2e:dev",
32
- "lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts --fix --ignore-path .gitignore",
32
+ "lint": "eslint --fix .",
33
33
  "format": "prettier --write ."
34
34
  },
35
35
  "license": "MIT",
@@ -43,10 +43,11 @@
43
43
  "vue": "^3.4.0"
44
44
  },
45
45
  "devDependencies": {
46
+ "@eslint/js": "^9.9.1",
46
47
  "@nabla/vite-plugin-eslint": "^2.0.4",
47
- "@rushstack/eslint-patch": "^1.10.4",
48
48
  "@signal24/openapi-client-codegen": "^1.1.0",
49
49
  "@tsconfig/node20": "^20.1.4",
50
+ "@types/eslint__js": "^8.42.3",
50
51
  "@types/jsdom": "^21.1.7",
51
52
  "@types/lodash": "^4.17.7",
52
53
  "@types/node": "^20.16.3",
@@ -58,7 +59,7 @@
58
59
  "@vue/tsconfig": "^0.5.1",
59
60
  "cypress": "^13.14.1",
60
61
  "date-fns": "^3.6.0",
61
- "eslint": "^8.57.0",
62
+ "eslint": "^9.9.1",
62
63
  "eslint-plugin-cypress": "^3.5.0",
63
64
  "eslint-plugin-simple-import-sort": "^12.1.1",
64
65
  "eslint-plugin-unused-imports": "^4.1.3",
@@ -66,13 +67,14 @@
66
67
  "jsdom": "^25.0.0",
67
68
  "lodash": "^4.17.21",
68
69
  "prettier": "^3.3.3",
69
- "sass": "^1.77.8",
70
+ "sass": "^1.78.0",
70
71
  "start-server-and-test": "^2.0.5",
71
72
  "type-fest": "^4.26.0",
72
- "typescript": "~5.5.4",
73
+ "typescript": "^5.5.4",
74
+ "typescript-eslint": "^8.4.0",
73
75
  "vite": "^5.4.3",
74
76
  "vitest": "^2.0.5",
75
- "vue": "^3.5.0",
77
+ "vue": "^3.5.1",
76
78
  "vue-tsc": "^2.1.4"
77
79
  },
78
80
  "packageManager": "yarn@4.0.2"
@@ -1,5 +1,5 @@
1
- import AlertModal from './alert-modal.vue';
2
1
  import { createOverlayInjection, presentOverlay, removeOverlayInjection } from './overlay-container';
2
+ import AlertModal from './vf-alert-modal.vue';
3
3
 
4
4
  interface IAlertOptions {
5
5
  title?: string;
@@ -1,8 +1,8 @@
1
- import VfAjaxSelect from './ajax-select.vue';
2
- import VfAlertModal from './alert-modal.vue';
3
- import VfEzSmartSelect from './ez-smart-select.vue';
4
- import VfModal from './modal.vue';
5
- import VfSmartSelect from './smart-select.vue';
1
+ import VfAjaxSelect from './vf-ajax-select.vue';
2
+ import VfAlertModal from './vf-alert-modal.vue';
3
+ import VfEzSmartSelect from './vf-ez-smart-select.vue';
4
+ import VfModal from './vf-modal.vue';
5
+ import VfSmartSelect from './vf-smart-select.vue';
6
6
 
7
7
  export * from './alert-helpers';
8
8
  export * from './overlay-container';
@@ -1,3 +1,4 @@
1
+ /* eslint-disable @typescript-eslint/no-explicit-any */
1
2
  import type { Writable } from 'type-fest';
2
3
  import {
3
4
  type AllowedComponentProps,
@@ -69,6 +70,7 @@ export type ObjectComponentProps<T extends Vue__ComponentPublicInstanceConstruct
69
70
  Omit<ObjectComponentConfig<T>['$props'], keyof VNodeProps | keyof AllowedComponentProps>
70
71
  >;
71
72
 
73
+ // eslint-disable-next-line @typescript-eslint/no-empty-object-type
72
74
  type ObjectOrDefault<T> = T extends object ? T : PropsWithCallback<{}>;
73
75
  export type OverlayComponent = Vue__ComponentPublicInstanceConstructor | ((props: any) => any);
74
76
  export type OverlayComponentConfig<T> = T extends Vue__ComponentPublicInstanceConstructor
@@ -124,7 +126,9 @@ export function createOverlayInjection<C extends OverlayComponent, R extends Com
124
126
  }
125
127
 
126
128
  export function dismissOverlayInjectionByInstance(instance: AnyComponentPublicInstance) {
127
- instance.$ && dismissOverlayInjectionByInternalInstance(instance.$);
129
+ if (instance.$) {
130
+ dismissOverlayInjectionByInternalInstance(instance.$);
131
+ }
128
132
  }
129
133
 
130
134
  export function dismissOverlayInjectionByInternalInstance(instance: ComponentInternalInstance) {
@@ -1,5 +1,5 @@
1
1
  import { createOverlayInjection, type OverlayInjection, removeOverlayInjection } from './overlay-container';
2
- import Toast, { type IToastOptions } from './toast.vue';
2
+ import Toast, { type IToastOptions } from './vf-toast.vue';
3
3
 
4
4
  export function showToast(options: IToastOptions) {
5
5
  const injection: OverlayInjection<typeof Toast, unknown> = createOverlayInjection(Toast, {
@@ -0,0 +1,61 @@
1
+ <template>
2
+ <select v-if="!renderOptions" disabled>
3
+ <option>{{ props.loadingText || 'Loading...' }}</option>
4
+ </select>
5
+ <select v-else v-model="selectedItem">
6
+ <option v-if="props.nullText" :value="null">{{ props.nullText }}</option>
7
+ <option v-for="(renderOption, index) in renderOptions" :key="index" :value="options?.[index]">
8
+ {{ renderOption }}
9
+ </option>
10
+ </select>
11
+ </template>
12
+
13
+ <script setup lang="ts" generic="T">
14
+ import { computed, defineEmits, defineProps, onMounted, ref, watch } from 'vue';
15
+
16
+ // todo: make type safe when Vue alpha is released
17
+
18
+ const props = defineProps<{
19
+ modelValue: T;
20
+ loadFn: () => Promise<T[]>;
21
+ nullText?: string;
22
+ loadingText?: string;
23
+ displayKey?: keyof T;
24
+ preprocesor?: (option: T) => string;
25
+ }>();
26
+
27
+ const emit = defineEmits<{
28
+ 'update:modelValue': [T];
29
+ }>();
30
+
31
+ const options = ref<T[] | null>(null);
32
+ const renderOptions = computed(() => {
33
+ if (!options.value) {
34
+ return null;
35
+ }
36
+
37
+ const result = options.value.map(option => {
38
+ const typedOption = option as T;
39
+ if (props.preprocesor) return props.preprocesor(typedOption);
40
+ if (props.displayKey) return typedOption[props.displayKey];
41
+ return '';
42
+ });
43
+
44
+ return result;
45
+ });
46
+
47
+ const selectedItem = ref<T | null>(props.modelValue ?? null);
48
+
49
+ watch(() => props.loadFn, load);
50
+ watch(
51
+ () => props.modelValue,
52
+ () => (selectedItem.value = props.modelValue)
53
+ );
54
+ watch(selectedItem, () => emit('update:modelValue', selectedItem.value));
55
+
56
+ async function load() {
57
+ options.value = await props.loadFn();
58
+ }
59
+
60
+ onMounted(load);
61
+ </script>
@@ -1,18 +1,18 @@
1
1
  <template>
2
2
  <Modal :class="['vf-alert', ...(classes ?? [])]">
3
- <template v-if="title" v-slot:header>
3
+ <template v-if="title" #header>
4
4
  <h1>{{ title }}</h1>
5
5
  </template>
6
6
 
7
7
  <div v-if="isHtml" :innerHtml="message" class="user-message"></div>
8
8
  <div v-else :innerText="textMessage"></div>
9
9
 
10
- <template v-if="!isBare" v-slot:footer>
10
+ <template v-if="!isBare" #footer>
11
11
  <template v-if="shouldConfirm">
12
- <button class="primary" @click="() => callback(true)" v-autofocus>Confirm</button>
12
+ <button v-autofocus class="primary" @click="() => callback(true)">Confirm</button>
13
13
  <button class="default" @click="() => callback(false)">Cancel</button>
14
14
  </template>
15
- <button v-else class="default" @click="() => callback(true)" v-autofocus>OK</button>
15
+ <button v-else v-autofocus class="default" @click="() => callback(true)">OK</button>
16
16
  </template>
17
17
  </Modal>
18
18
  </template>
@@ -21,7 +21,7 @@
21
21
  import { computed } from 'vue';
22
22
 
23
23
  import { formatError } from '../helpers/error';
24
- import Modal from './modal.vue';
24
+ import Modal from './vf-modal.vue';
25
25
 
26
26
  const props = defineProps<{
27
27
  isBare?: boolean;
@@ -6,16 +6,19 @@
6
6
  import { isEqual } from 'lodash';
7
7
  import { computed, ref, watch } from 'vue';
8
8
 
9
- import VfSmartSelect from './smart-select.vue';
9
+ import VfSmartSelect from './vf-smart-select.vue';
10
10
 
11
- type GenericObject = { [key: string]: any };
11
+ interface IComputedOption {
12
+ value: string;
13
+ label: string;
14
+ }
12
15
 
13
16
  const props = defineProps<{
14
17
  modelValue: string | null | undefined;
15
18
  nullTitle?: string;
16
19
  placeholder?: string;
17
- options: Record<string, string> | string[];
18
- formatter?: (value: any) => string;
20
+ options: { [K: string]: string } | string[];
21
+ formatter?: (item: IComputedOption) => string;
19
22
  }>();
20
23
 
21
24
  const computedOpts = computed(() => {
@@ -29,16 +32,16 @@ const computedOpts = computed(() => {
29
32
 
30
33
  const ezFormatter = computed(() => {
31
34
  if (props.formatter) {
32
- return (o: GenericObject) => props.formatter?.(o.label);
35
+ return (o: IComputedOption) => props.formatter!(o);
33
36
  }
34
- return (o: GenericObject) => o.label;
37
+ return (o: IComputedOption) => o.label;
35
38
  });
36
39
 
37
40
  const emit = defineEmits<{
38
41
  (e: 'update:modelValue', value: string | null): void;
39
42
  }>();
40
43
 
41
- const selectedItem = ref<GenericObject | null>(computedOpts.value.find(o => o.value === props.modelValue) ?? null);
44
+ const selectedItem = ref<IComputedOption | null>(computedOpts.value.find(o => o.value === props.modelValue) ?? null);
42
45
  watch(
43
46
  () => props.modelValue,
44
47
  value => {
@@ -46,6 +49,7 @@ watch(
46
49
  }
47
50
  );
48
51
  watch(selectedItem, value => {
49
- emit('update:modelValue', value ? (computedOpts.value.find(o => isEqual(o, value))?.value ?? null) : null);
52
+ const emitValue = value ? computedOpts.value.find(o => isEqual(o, value))?.value : null;
53
+ emit('update:modelValue', emitValue ?? null);
50
54
  });
51
55
  </script>
@@ -1,6 +1,6 @@
1
1
  <template>
2
- <div :id="id" class="vf-overlay vf-modal-wrap" :class="classList" ref="overlay">
3
- <form action="." class="vf-modal" :class="{ scrolls }" @submit.prevent="$emit('formSubmit')" ref="form">
2
+ <div :id="id" ref="overlay" class="vf-overlay vf-modal-wrap" :class="classList">
3
+ <form ref="form" action="." class="vf-modal" :class="{ scrolls }" @submit.prevent="$emit('formSubmit')">
4
4
  <div v-if="$slots.header" class="vf-modal-header">
5
5
  <slot name="header" />
6
6
  <i v-if="props.closeX" class="close" @click="closeParent"></i>
@@ -57,7 +57,7 @@ onBeforeUnmount(() => {
57
57
  window.removeEventListener('keydown', handleEscapeKey);
58
58
 
59
59
  const areOtherModalsOpen = document.body.querySelectorAll('.vf-modal').length > 0;
60
- areOtherModalsOpen || document.body.classList.remove('vf-modal-open');
60
+ if (!areOtherModalsOpen) document.body.classList.remove('vf-modal-open');
61
61
  });
62
62
 
63
63
  function handleOverlayClick(e: MouseEvent) {