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

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