@falcondev-oss/nuxt-layers-base 0.40.0 → 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,8 +1,8 @@
|
|
|
1
1
|
<script setup lang="ts" generic="const O extends RadioGroupItem[], R extends ConfirmModalResult<O>">
|
|
2
2
|
import type { ButtonProps, RadioGroupItem, RadioGroupProps } from '@nuxt/ui'
|
|
3
3
|
import type { ComponentPublicInstance } from 'vue'
|
|
4
|
-
import type { ConfirmModalResult } from '
|
|
5
|
-
import type { templateParts } from '
|
|
4
|
+
import type { ConfirmModalResult } from '../../composables/confirm'
|
|
5
|
+
import type { templateParts } from '../../utils/display'
|
|
6
6
|
|
|
7
7
|
export type ConfirmModalProps<O extends RadioGroupItem[]> = {
|
|
8
8
|
title: string
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { RadioGroupItem } from '@nuxt/ui'
|
|
2
|
-
import type { ConfirmModalProps } from '
|
|
2
|
+
import type { ConfirmModalProps } from '../components/modals/ConfirmModal.vue'
|
|
3
3
|
import { LazyConfirmModal } from '#components'
|
|
4
4
|
|
|
5
5
|
export type ConfirmModalSuccess<O extends RadioGroupItem[]> = 0 extends O['length']
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
|
|
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
|
|
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
|
-
*
|
|
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
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
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
|
|
45
|
-
|
|
46
|
-
|
|
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
|
-
|
|
61
|
-
|
|
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<
|
|
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> &
|
|
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
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
175
|
+
// oxlint-disable-next-line typescript/no-unsafe-argument
|
|
121
176
|
const opts = options_({} as any)
|
|
122
|
-
//
|
|
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/nuxt.config.ts
CHANGED
|
@@ -7,6 +7,14 @@ export default defineNuxtConfig({
|
|
|
7
7
|
compatibilityDate: '2025-07-15',
|
|
8
8
|
devtools: { enabled: true },
|
|
9
9
|
|
|
10
|
+
hooks: {
|
|
11
|
+
// consumers only pick up `app/**/*` through an `include` glob that their own
|
|
12
|
+
// tsconfig may exclude, so reference the layer's global augmentations explicitly
|
|
13
|
+
'prepare:types': ({ references }) => {
|
|
14
|
+
references.push({ path: path.join(currentDir, './app/index.d.ts') })
|
|
15
|
+
},
|
|
16
|
+
},
|
|
17
|
+
|
|
10
18
|
// dev
|
|
11
19
|
typescript: {
|
|
12
20
|
strict: true,
|