@signal24/vue-foundation 4.30.5 → 4.30.8

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.30.5",
4
+ "version": "4.30.8",
5
5
  "description": "Common components, directives, and helpers for Vue 3 apps",
6
6
  "module": "./dist/vue-foundation.es.js",
7
7
  "exports": {
@@ -31,6 +31,9 @@
31
31
  "format": "prettier --write ."
32
32
  },
33
33
  "license": "MIT",
34
+ "repository": {
35
+ "url": "https://github.com/signal24/vue-foundation"
36
+ },
34
37
  "dependencies": {
35
38
  "currency.js": "^2.0.4",
36
39
  "mark.js": "^8.11.1",
@@ -43,37 +46,37 @@
43
46
  "vue": "^3.4.0"
44
47
  },
45
48
  "devDependencies": {
46
- "@eslint/js": "9.39.1",
49
+ "@eslint/js": "9.39.2",
47
50
  "@nabla/vite-plugin-eslint": "^2.0.6",
48
51
  "@signal24/openapi-client-codegen": "^2.6.2",
49
- "@tsconfig/node22": "^22.0.2",
52
+ "@tsconfig/node22": "^22.0.5",
50
53
  "@types/jsdom": "^27.0.0",
51
- "@types/lodash": "^4.17.20",
54
+ "@types/lodash": "^4.17.21",
52
55
  "@types/mark.js": "^8",
53
- "@types/node": "^24.10.0",
56
+ "@types/node": "^25.0.3",
54
57
  "@types/uuid": "^11.0.0",
55
- "@vitejs/plugin-vue": "^6.0.1",
58
+ "@vitejs/plugin-vue": "^6.0.3",
56
59
  "@vue/eslint-config-prettier": "^10.2.0",
57
60
  "@vue/eslint-config-typescript": "^14.6.0",
58
61
  "@vue/test-utils": "^2.4.6",
59
62
  "@vue/tsconfig": "^0.8.1",
60
63
  "date-fns": "^4.1.0",
61
- "eslint": "9.39.1",
64
+ "eslint": "9.39.2",
62
65
  "eslint-plugin-simple-import-sort": "^12.1.1",
63
66
  "eslint-plugin-unused-imports": "^4.3.0",
64
- "eslint-plugin-vue": "^10.5.1",
65
- "jsdom": "^27.1.0",
67
+ "eslint-plugin-vue": "^10.6.2",
68
+ "jsdom": "^27.4.0",
66
69
  "lodash": "^4.17.21",
67
- "prettier": "^3.6.2",
68
- "sass": "^1.93.3",
69
- "start-server-and-test": "^2.1.2",
70
- "type-fest": "^5.2.0",
70
+ "prettier": "^3.7.4",
71
+ "sass": "^1.97.2",
72
+ "start-server-and-test": "^2.1.3",
73
+ "type-fest": "^5.3.1",
71
74
  "typescript": "~5.9",
72
- "typescript-eslint": "^8.46.3",
73
- "vite": "^7.2.1",
74
- "vitest": "^4.0.7",
75
- "vue": "^3.5.23",
76
- "vue-tsc": "^3.1.3"
75
+ "typescript-eslint": "^8.52.0",
76
+ "vite": "^7.3.1",
77
+ "vitest": "^4.0.16",
78
+ "vue": "^3.5.26",
79
+ "vue-tsc": "^3.2.2"
77
80
  },
78
81
  "packageManager": "yarn@4.9.2"
79
82
  }
@@ -1,14 +1,12 @@
1
1
  /* eslint-disable @typescript-eslint/no-explicit-any */
2
- import type { Writable } from 'type-fest';
3
2
  import {
4
3
  type AllowedComponentProps,
4
+ type Component,
5
+ type ComponentCustomProps,
5
6
  type ComponentInternalInstance,
6
- type ComponentPublicInstance,
7
- type ComputedOptions,
8
7
  defineComponent,
9
8
  h,
10
9
  markRaw,
11
- type MethodOptions,
12
10
  type Raw,
13
11
  reactive,
14
12
  renderList,
@@ -23,23 +21,23 @@ import { VfOptions } from '@/config';
23
21
  import OverlayAnchor from './overlay-anchor.vue';
24
22
  import type { OverlayAnchorOptions } from './overlay-types';
25
23
 
26
- interface OverlayOptions<C extends OverlayComponent, R extends ComponentReturn<C>> {
24
+ interface OverlayOptions<C extends Component, R extends ComponentReturn<C>> {
27
25
  anchor?: OverlayAnchorOptions;
28
26
  onCallback?: (result: R) => void | Promise<boolean>;
29
27
  }
30
28
 
31
- export interface OverlayInjection<C extends OverlayComponent, R extends ComponentReturn<C>> {
29
+ export interface OverlayInjection<C extends Component> {
32
30
  id: string;
33
- component: OverlayComponentUnwrapped<C>;
34
- props: OverlayComponentProps<C>;
35
- options: OverlayOptions<C, R>;
31
+ component: Raw<C>;
32
+ props: { callback?: () => void } & OverlayComponentProps<C>;
33
+ options: OverlayOptions<C, any>;
36
34
  vnode: VNode;
37
35
  wrapperVnode?: VNode;
38
36
  }
39
37
 
40
38
  let overlayCount = 0;
41
39
 
42
- const OverlayInjections: OverlayInjection<any, any>[] = reactive([]);
40
+ const OverlayInjections: OverlayInjection<any>[] = reactive([]);
43
41
  watch(OverlayInjections, () => {
44
42
  VfOptions.onOverlaysChanged?.(OverlayInjections.length);
45
43
  });
@@ -55,57 +53,47 @@ export const OverlayContainer = defineComponent({
55
53
  }
56
54
  });
57
55
 
58
- // copied in from Vue since it's not exported
59
- // tood: it may be a lot easier than this. see the docs for props passed to "h"
60
- export type Vue__ComponentPublicInstanceConstructor<
61
- T extends ComponentPublicInstance<Props, RawBindings, D, C, M> = ComponentPublicInstance<any>,
62
- Props = any,
63
- RawBindings = any,
64
- D = any,
65
- C extends ComputedOptions = ComputedOptions,
66
- M extends MethodOptions = MethodOptions
67
- > = {
68
- __isFragment?: never;
69
- __isTeleport?: never;
70
- __isSuspense?: never;
71
- new (...args: any[]): T;
56
+ export type AnyComponentPublicInstance = { $?: ComponentInternalInstance };
57
+
58
+ // Handle both regular and generic components
59
+ type ExtractComponentProps<C> = C extends new (...args: any) => any
60
+ ? InstanceType<C>['$props']
61
+ : C extends (props: infer P, ...args: any) => any
62
+ ? P
63
+ : C extends { __props?: infer P }
64
+ ? P
65
+ : never;
66
+
67
+ // Remove Vue's internal prop types and the Record<string, unknown> index signature
68
+ type CleanProps<P> = {
69
+ [K in keyof P as K extends keyof (VNodeProps & AllowedComponentProps & ComponentCustomProps) ? never : string extends K ? never : K]: P[K];
72
70
  };
73
71
 
74
- export type ObjectComponentConfig<T extends Vue__ComponentPublicInstanceConstructor> =
75
- T extends Vue__ComponentPublicInstanceConstructor<infer P> ? P : never;
76
- export type ObjectComponentProps<T extends Vue__ComponentPublicInstanceConstructor> = Writable<
77
- Omit<ObjectComponentConfig<T>['$props'], keyof VNodeProps | keyof AllowedComponentProps>
78
- >;
79
-
80
- // eslint-disable-next-line @typescript-eslint/no-empty-object-type
81
- type ObjectOrDefault<T> = T extends object ? T : PropsWithCallback<{}>;
82
- export type OverlayComponent = Vue__ComponentPublicInstanceConstructor | ((props: any) => any);
83
- export type OverlayComponentConfig<T> = T extends Vue__ComponentPublicInstanceConstructor
84
- ? {
85
- props: ObjectComponentProps<T>;
86
- component: Raw<T>;
87
- }
88
- : T extends (props: infer P) => any
89
- ? {
90
- props: Omit<ObjectOrDefault<P>, keyof VNodeProps | keyof AllowedComponentProps>;
91
- component: T;
92
- }
93
- : never;
94
- export type OverlayComponentUnwrapped<T extends OverlayComponent> = OverlayComponentConfig<T>['component'];
95
- export type OverlayComponentProps<T extends OverlayComponent> = OverlayComponentConfig<T>['props'];
96
-
97
- interface PropsWithCallback<T> {
98
- callback?: (result: T) => void;
99
- }
100
- type ComponentReturn<M extends OverlayComponent> = OverlayComponentProps<M> extends PropsWithCallback<infer R> ? R : never;
72
+ // Check if component has a callback prop with correct signature
73
+ type HasCallbackProp<C> =
74
+ CleanProps<ExtractComponentProps<C>> extends {
75
+ callback: (result: any) => void;
76
+ }
77
+ ? true
78
+ : false;
101
79
 
102
- export type AnyComponentPublicInstance = { $?: ComponentInternalInstance };
80
+ // Constraint type - resolves to C if valid, never if not
81
+ type OverlayComponent<C extends Component> = HasCallbackProp<C> extends true ? C : never;
82
+
83
+ type ComponentReturn<C> =
84
+ CleanProps<ExtractComponentProps<C>> extends {
85
+ callback: (result: infer R) => void;
86
+ }
87
+ ? R
88
+ : never;
89
+
90
+ type OverlayComponentProps<C> = CleanProps<ExtractComponentProps<C>>;
103
91
 
104
- export function createOverlayInjection<C extends OverlayComponent, R extends ComponentReturn<C>>(
105
- component: C,
92
+ export function createOverlayInjection<C extends Component, R extends ComponentReturn<C>>(
93
+ component: OverlayComponent<C>,
106
94
  props: OverlayComponentProps<C>,
107
95
  options?: OverlayOptions<C, R>
108
- ): OverlayInjection<C, R> {
96
+ ): OverlayInjection<C> {
109
97
  // create or reconfigure the existing overlay target
110
98
  // re-injecting every time keeps the overlay container at the very end of the DOM
111
99
  const targetEl = document.getElementById('vf-overlay-target') ?? document.createElement('div');
@@ -119,9 +107,9 @@ export function createOverlayInjection<C extends OverlayComponent, R extends Com
119
107
  const wrapperVnode = options?.anchor ? h(OverlayAnchor, { overlayId, anchor: options.anchor }, () => [vnode]) : undefined;
120
108
 
121
109
  // todo: dunno what's going on with types here
122
- const injection: OverlayInjection<C, R> = {
110
+ const injection: OverlayInjection<C> = {
123
111
  id: overlayId,
124
- component: rawComponent as any,
112
+ component: rawComponent,
125
113
  props,
126
114
  options: options ?? {},
127
115
  vnode,
@@ -148,7 +136,7 @@ export function dismissOverlayInjectionByInternalInstance(instance: ComponentInt
148
136
  export function dismissOverlayInjectionByVnode(vnode: VNode) {
149
137
  const injectionIdx = OverlayInjections.findIndex(i => i.vnode.component === vnode.component);
150
138
  if (injectionIdx >= 0) {
151
- OverlayInjections[injectionIdx]!.props.callback();
139
+ OverlayInjections[injectionIdx]!.props.callback?.();
152
140
  return true;
153
141
  }
154
142
  return false;
@@ -157,26 +145,26 @@ export function dismissOverlayInjectionByVnode(vnode: VNode) {
157
145
  export function dismissOverlayInjectionById(id: string) {
158
146
  const injectionIdx = OverlayInjections.findIndex(i => i.id === id);
159
147
  if (injectionIdx >= 0) {
160
- OverlayInjections[injectionIdx]!.props.callback();
148
+ OverlayInjections[injectionIdx]!.props.callback?.();
161
149
  return true;
162
150
  }
163
151
  return false;
164
152
  }
165
153
 
166
- export function removeOverlayInjection(injection: OverlayInjection<any, any>) {
154
+ export function removeOverlayInjection(injection: OverlayInjection<any>) {
167
155
  const index = OverlayInjections.indexOf(injection);
168
156
  if (index >= 0) {
169
157
  OverlayInjections.splice(index, 1);
170
158
  }
171
159
  }
172
160
 
173
- export async function presentOverlay<C extends OverlayComponent, R extends ComponentReturn<C>>(
174
- component: C,
161
+ export async function presentOverlay<C extends Component, R extends ComponentReturn<C>>(
162
+ component: OverlayComponent<C>,
175
163
  props: Omit<OverlayComponentProps<C>, 'callback'>,
176
164
  options?: OverlayOptions<C, R>
177
165
  ): Promise<R | undefined> {
178
166
  return new Promise<R>(resolve => {
179
- let overlayInjection: OverlayInjection<C, R> | null = null;
167
+ let overlayInjection: OverlayInjection<C> | null = null;
180
168
  const callback = async (result: R) => {
181
169
  if (options?.onCallback) {
182
170
  const hookResult = options.onCallback(result);
@@ -197,12 +185,12 @@ export async function presentOverlay<C extends OverlayComponent, R extends Compo
197
185
  });
198
186
  }
199
187
 
200
- export async function updateOverlayProps<C extends OverlayComponent>(
201
- injection: OverlayInjection<C, any>,
188
+ export async function updateOverlayProps<C extends Component>(
189
+ injection: OverlayInjection<C>,
202
190
  props: Partial<Omit<OverlayComponentProps<C>, 'callback'>>
203
191
  ) {
204
192
  const targetProps = injection.vnode.component!.props;
205
193
  for (const key in props) {
206
- targetProps[key] = props[key];
194
+ targetProps[key] = (props as any)[key];
207
195
  }
208
196
  }
@@ -2,7 +2,7 @@ import { createOverlayInjection, type OverlayInjection, removeOverlayInjection }
2
2
  import Toast, { type IToastOptions } from './vf-toast.vue';
3
3
 
4
4
  export function showToast(options: IToastOptions) {
5
- const injection: OverlayInjection<typeof Toast, unknown> = createOverlayInjection(Toast, {
5
+ const injection: OverlayInjection<typeof Toast> = createOverlayInjection(Toast, {
6
6
  ...options,
7
7
  callback: () => removeOverlayInjection(injection)
8
8
  });
@@ -1,4 +1,4 @@
1
- import { cloneDeep } from 'lodash';
1
+ import { cloneDeep, isEqual, isMatch } from 'lodash';
2
2
 
3
3
  export function cloneProp<T>(prop: T | undefined | null, fallback: T): T {
4
4
  if (prop !== undefined && prop !== null) {
@@ -22,3 +22,66 @@ export function nullifyEmptyInputs<T extends Record<string, unknown>, K extends
22
22
  export function isNotNullOrUndefined<T>(value: T | null | undefined): value is T {
23
23
  return value !== null && value !== undefined;
24
24
  }
25
+
26
+ export function objectKeys<T extends object>(object: T): (keyof T)[] {
27
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
28
+ return Object.keys(object) as any[];
29
+ }
30
+
31
+ export function objectAssign<T extends object>(object: T, ...values: Partial<T>[]): T {
32
+ return Object.assign(object, ...values);
33
+ }
34
+
35
+ type Entries<T> = {
36
+ [K in keyof T]: [K, T[K]];
37
+ }[keyof T][];
38
+ export function objectEntries<T extends object>(object: T): Entries<T> {
39
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
40
+ return Object.entries(object) as any;
41
+ }
42
+
43
+ export function extractValues<T extends object, K extends readonly (keyof T)[]>(state: T, fields: K): Pick<T, K[number]> {
44
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
45
+ const result: Pick<T, K[number]> = {} as any;
46
+ for (const key of fields) {
47
+ if (state[key] !== undefined) {
48
+ result[key] = state[key];
49
+ }
50
+ }
51
+ return result;
52
+ }
53
+
54
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
55
+ function doesMatch(original: any, updated: any, method?: 'equals' | 'matches'): boolean {
56
+ if (method === 'matches' && typeof original === 'object' && typeof updated === 'object') {
57
+ return isMatch(original, updated);
58
+ }
59
+ return isEqual(original, updated);
60
+ }
61
+
62
+ export function extractUpdates<T extends object>(state: T, updates: Partial<T>, fields?: Array<keyof T>, method?: 'equals' | 'matches'): Partial<T> {
63
+ const result: Partial<T> = {};
64
+ const updateFields = fields ?? objectKeys(updates);
65
+ for (const key of updateFields) {
66
+ if (updates[key] !== undefined && !doesMatch(state[key], updates[key], method)) {
67
+ result[key] = updates[key];
68
+ }
69
+ }
70
+ return result;
71
+ }
72
+
73
+ export function patchObject<T extends object>(state: T, updates: Partial<T>, fields?: Array<keyof T>, method?: 'equals' | 'matches'): T {
74
+ const effectiveUpdates = extractUpdates(state, updates, fields, method);
75
+ objectAssign(state, effectiveUpdates);
76
+ return state;
77
+ }
78
+
79
+ export function extractKV<T, K extends keyof T, V extends keyof T>(arr: T[], keyCol: K, valCol: V) {
80
+ return arr.reduce(
81
+ (acc, cur) => {
82
+ acc[cur[keyCol] as string] = cur[valCol];
83
+ return acc;
84
+ },
85
+ {} as Record<string, T[V]>
86
+ );
87
+ }