@falcondev-oss/nuxt-layers-base 0.40.1 → 0.40.3

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.
@@ -39,9 +39,9 @@ const props = defineProps<{
39
39
  const slots = defineSlots<
40
40
  {
41
41
  'default': any
42
- 'navbar-title': any
43
- 'navbar-trailing': any
44
- 'navbar-actions': any
42
+ 'navbar-title'?: any
43
+ 'navbar-trailing'?: any
44
+ 'navbar-actions'?: any
45
45
  } & AddPropertyPrefix<DashboardNavbarSlots, 'navbar'>
46
46
  >()
47
47
 
@@ -29,9 +29,9 @@ defineProps<{
29
29
  }>()
30
30
 
31
31
  const slots = defineSlots<{
32
- default: any
33
- logo: any
34
- icon: any
32
+ 'default': any
33
+ 'logo'?: any
34
+ 'icon'?: any
35
35
  }>()
36
36
 
37
37
  const config = useRuntimeConfig()
@@ -10,7 +10,7 @@ export function usePreventPageLeave(
10
10
  ) {
11
11
  useEventListener('beforeunload', (event) => {
12
12
  if (!toValue(preventPageLeave)) return
13
- // eslint-disable-next-line ts/no-unsafe-member-access
13
+
14
14
  event.returnValue = opts?.leaveDescription
15
15
  return opts?.leaveDescription
16
16
  })
@@ -1,6 +1,7 @@
1
- /* eslint-disable ts/no-empty-object-type */
2
- import type { AllUnionFields, UnionToTuple } from 'type-fest'
1
+ /* eslint-disable ts/no-empty-object-type, ts/no-unsafe-return, ts/no-unsafe-argument */
2
+ import type { AllUnionFields, Simplify } from 'type-fest'
3
3
  import type {
4
+ Attrs,
4
5
  ComponentOptionsMixin,
5
6
  CreateComponentPublicInstanceWithMixins,
6
7
  EmitsToProps,
@@ -8,13 +9,17 @@ import type {
8
9
  PublicProps,
9
10
  RenderFunction,
10
11
  SetupContext,
11
- Slots as SlotOptions,
12
12
  SlotsType,
13
13
  } from 'vue'
14
+ import { defineComponent } from 'vue'
14
15
 
15
16
  declare module 'vue' {
16
17
  interface ComponentCustomProps {
17
- vSlots?: SlotOptions
18
+ // Deliberately loose: this only makes `vSlots` an accepted prop name on every
19
+ // component. `Slots` would reject it for any component whose slots are declared as an
20
+ // interface without an index signature (most of @nuxt/ui). The real check is the
21
+ // `vSlots()` helper below, which types the object against the target's own slots.
22
+ vSlots?: Record<string, any>
18
23
  }
19
24
  }
20
25
 
@@ -22,6 +27,18 @@ interface ComponentTypes {
22
27
  props?: Record<string, any>
23
28
  emits?: ObjectEmitsOptions
24
29
  slots?: Record<string, any>
30
+ /**
31
+ * Prop names to register when they are deliberately fewer than `keyof props`; the rest
32
+ * arrive as `attrs`. See `docs/agents/vue-to-tsx-migration.md` for when that is right.
33
+ */
34
+ propKeys?: string
35
+ /**
36
+ * What `ctx.expose()` publishes, so parents holding a template ref see it.
37
+ *
38
+ * `expose()` alone only works at runtime; without this the instance type has no trace
39
+ * of it and `ref.value.selectDate()` is a type error at every call site.
40
+ */
41
+ expose?: Record<string, any>
25
42
  }
26
43
 
27
44
  /**
@@ -35,15 +52,38 @@ type Field<T extends ComponentTypes, K extends keyof ComponentTypes> = K extends
35
52
  : {}
36
53
 
37
54
  /**
38
- * Keys of `T` as a readonly tuple, or `readonly []` when `T` has no keys.
55
+ * Every key of `T`, flattened across union members. `never` when `T` has no keys,
56
+ * which makes `readonly Keys<T>[]` accept only `[]`.
57
+ */
58
+ type Keys<T> = [keyof T] extends [never] ? never : Extract<keyof AllUnionFields<T>, string>
59
+
60
+ /** The runtime prop names: `propKeys` when declared, the keys of `props` otherwise. */
61
+ type PropKeys<T extends ComponentTypes> = 'propKeys' extends keyof T
62
+ ? Extract<NonNullable<T['propKeys']>, string>
63
+ : Keys<Field<T, 'props'>>
64
+
65
+ /** Props not registered in `propKeys` arrive through `attrs` with Vue's `unknown` index signature. */
66
+ type AttrProps<T extends ComponentTypes> = Simplify<Omit<Field<T, 'props'>, PropKeys<T>> & Attrs>
67
+
68
+ type EmitKeys<T extends ComponentTypes> = Keys<Field<T, 'emits'>>
69
+
70
+ /** The shape no array satisfies; generic so the alias head names the missing keys. */
71
+ type MissingEntries<K extends string> = { readonly [P in K]: true }
72
+
73
+ /**
74
+ * `unknown` when `Given` covers every key in `All`, otherwise an object that no array
75
+ * satisfies. Intersected onto the `props`/`emits` parameter it turns a forgotten key
76
+ * into an error that names the key.
39
77
  *
40
- * `keyof {}` is `never` and `UnionToTuple<never>` resolves to `never` (not `[]`),
41
- * so the empty case is guarded explicitly. Without it an empty `props`/`emits`
42
- * declaration (e.g. `emits: {}`) poisons inference of the surrounding options object.
78
+ * Deliberately *not* `UnionToTuple`: that fixes an order which TypeScript derives from
79
+ * union member order, an implementation detail that shifts when unrelated declarations
80
+ * move. A component would compile today and demand a reshuffled array tomorrow.
43
81
  */
44
- type KeysTuple<T> = [keyof T] extends [never]
45
- ? readonly []
46
- : Readonly<UnionToTuple<keyof AllUnionFields<T>>>
82
+ export type Exhaustive<All extends string, Given extends readonly string[]> = [
83
+ Exclude<All, Given[number]>,
84
+ ] extends [never]
85
+ ? unknown
86
+ : MissingEntries<Exclude<All, Given[number]>>
47
87
 
48
88
  /**
49
89
  * Vue's own `emit` type (`SetupContext<E>['emit']`) for declared emits, but an empty
@@ -57,12 +97,23 @@ type StrictEmitFn<E extends ObjectEmitsOptions> = [keyof E] extends [never]
57
97
 
58
98
  /** Runtime config for a component, derived from its declared types `T`. */
59
99
  interface SetupConfig<T extends ComponentTypes> {
60
- props: KeysTuple<Field<T, 'props'>>
61
- emits: KeysTuple<Field<T, 'emits'>>
100
+ /**
101
+ * The component's name, as Vue Devtools and warning traces show it.
102
+ *
103
+ * Without it every component built here reports as `<Setup>`, because the setup
104
+ * function it is derived from is anonymous.
105
+ */
106
+ name?: string
107
+ /** Vue's `inheritAttrs`. Set to `false` to place `ctx.attrs` yourself. */
108
+ inheritAttrs?: boolean
62
109
  setup: (
63
110
  props: Field<T, 'props'> & EmitsToProps<Field<T, 'emits'>>,
64
- ctx: Omit<SetupContext<Field<T, 'emits'>, SlotsType<Partial<Field<T, 'slots'>>>>, 'emit'> & {
111
+ ctx: Omit<
112
+ SetupContext<Field<T, 'emits'>, SlotsType<Partial<Field<T, 'slots'>>>>,
113
+ 'emit' | 'attrs'
114
+ > & {
65
115
  emit: StrictEmitFn<Field<T, 'emits'>>
116
+ attrs: AttrProps<T>
66
117
  },
67
118
  ) => RenderFunction | Promise<RenderFunction>
68
119
  }
@@ -72,7 +123,10 @@ interface SetupConfig<T extends ComponentTypes> {
72
123
  // be returned as a raw object literal. The key is a readable string so bypassing it
73
124
  // reports `Property '"use the options() helper"' is missing`.
74
125
  type OptionsBrand = { readonly ['use the options() helper']: true }
75
- type ViaOptions<T extends ComponentTypes> = SetupConfig<T> & OptionsBrand
126
+ type ViaOptions<T extends ComponentTypes> = SetupConfig<T> & {
127
+ props: readonly PropKeys<T>[]
128
+ emits: readonly EmitKeys<T>[]
129
+ } & OptionsBrand
76
130
 
77
131
  // `defineSetupComponent`'s callback must return an `options()` result. This shape is
78
132
  // intentionally independent of `T`: `options()` already validated everything and typed
@@ -81,52 +135,55 @@ type ViaOptions<T extends ComponentTypes> = SetupConfig<T> & OptionsBrand
81
135
  type LooseSetupConfig = {
82
136
  props: readonly string[]
83
137
  emits: readonly string[]
138
+ name?: string
139
+ inheritAttrs?: boolean
84
140
  setup: (...args: any) => any
85
141
  } & OptionsBrand
86
142
 
87
- /**
88
- * Validates the runtime `config` against the declared types `_` and types `setup`.
89
- *
90
- * `_` binds the declared types `T`, so `config` is checked as a plain argument (not as
91
- * an inferred callback return) — which means key mismatches are reported locally, right
92
- * on the offending `props`/`emits`/`setup` property.
93
- */
94
- export function options<const T extends ComponentTypes>(
143
+ /** `_` binds `T`, so key mismatches are reported on the offending config property. */
144
+ export function options<
145
+ const T extends ComponentTypes,
146
+ const P extends readonly PropKeys<T>[],
147
+ const E extends readonly EmitKeys<T>[],
148
+ >(
95
149
  _: T,
96
- config: SetupConfig<T>,
150
+ config: SetupConfig<T> & {
151
+ props: P & Exhaustive<PropKeys<T>, P>
152
+ emits: E & Exhaustive<EmitKeys<T>, E>
153
+ },
97
154
  ): ViaOptions<T> {
98
- return config as ViaOptions<T>
155
+ return config as unknown as ViaOptions<T>
99
156
  }
100
157
 
101
158
  export function defineSetupComponent<const T extends ComponentTypes>(
102
159
  options_: (opts: T) => LooseSetupConfig,
103
- ): new (
104
- props: Field<T, 'props'>,
105
- ) => CreateComponentPublicInstanceWithMixins<
160
+ ): new (props: Field<T, 'props'>) => CreateComponentPublicInstanceWithMixins<
106
161
  Field<T, 'props'> & EmitsToProps<Field<T, 'emits'>>,
107
- {},
162
+ Field<T, 'expose'>,
108
163
  {},
109
164
  {},
110
165
  {},
111
166
  ComponentOptionsMixin,
112
167
  ComponentOptionsMixin,
113
168
  Field<T, 'emits'>,
114
- PublicProps & { vSlots?: Field<T, 'slots'> },
169
+ // `Partial`, to match `SlotsType<Partial<...>>` below and the partial type `vSlots()`
170
+ PublicProps & { vSlots?: Partial<Field<T, 'slots'>> },
115
171
  {},
116
172
  false,
117
173
  {},
118
174
  SlotsType<Partial<Field<T, 'slots'>>>
119
175
  > {
120
- // eslint-disable-next-line ts/no-unsafe-argument
176
+ // oxlint-disable-next-line typescript/no-unsafe-argument
121
177
  const opts = options_({} as any)
122
- // eslint-disable-next-line ts/no-unsafe-return, ts/no-unsafe-argument
178
+ // oxlint-disable-next-line typescript/no-unsafe-return, typescript/no-unsafe-argument
123
179
  return defineComponent(opts.setup as any, {
124
180
  props: opts.props as unknown as string[],
125
181
  emits: opts.emits as unknown as string[],
182
+ name: opts.name,
183
+ inheritAttrs: opts.inheritAttrs,
126
184
  }) as any
127
185
  }
128
186
 
129
- // https://github.com/vuejs/language-tools/blob/master/packages/component-type-helpers/index.ts
130
187
  type ComponentSlots<T> = T extends new (...args: any) => { $slots: infer S }
131
188
  ? NonNullable<S>
132
189
  : T extends (props: any, ctx: { slots: infer S; attrs: any; emit: any }, ...args: any) => any
@@ -136,3 +193,33 @@ type ComponentSlots<T> = T extends new (...args: any) => { $slots: infer S }
136
193
  export function vSlots<C>(component: C, slots: ComponentSlots<C>) {
137
194
  return slots
138
195
  }
196
+
197
+ /**
198
+ * Vue's short emit declaration (`{ change: [event: Event] }`) rewritten as the call
199
+ * signatures `ComponentTypes['emits']` expects. Component libraries ship the short form,
200
+ * so a wrapper forwarding their emits would otherwise have to restate every signature.
201
+ */
202
+ export type AsEmits<E> = {
203
+ [K in keyof E]: E[K] extends readonly any[] ? (...args: E[K]) => void : never
204
+ }
205
+
206
+ /**
207
+ * A reusable, exhaustively checked list of the prop names of `T`.
208
+ *
209
+ * `defineSetupComponent` needs every prop spelled out at runtime, which is pure
210
+ * repetition for the many wrapper components that re-declare a shared props type
211
+ * (`FormGroupProps`, `GenericInputProps`, ...). Declare the list once here and spread
212
+ * it into `options({ props: [...] })`; the spread keeps the literal types, so the
213
+ * component's own exhaustiveness check still applies.
214
+ *
215
+ * ```ts
216
+ * export const formGroupPropNames = propNames<FormGroupProps>()(['label', 'help'])
217
+ * // ...
218
+ * props: [...formGroupPropNames, 'field']
219
+ * ```
220
+ */
221
+ export function propNames<T>() {
222
+ return <const P extends readonly Extract<keyof T, string>[]>(
223
+ names: P & Exhaustive<Extract<keyof T, string>, P>,
224
+ ): P => names
225
+ }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@falcondev-oss/nuxt-layers-base",
3
3
  "type": "module",
4
- "version": "0.40.1",
4
+ "version": "0.40.3",
5
5
  "description": "Nuxt layer with lots of useful helpers and @nuxt/ui components",
6
6
  "license": "MIT",
7
7
  "repository": {