diff --git a/core/stdlib/package.json b/core/stdlib/package.json index 79d0dc5..ae85cec 100644 --- a/core/stdlib/package.json +++ b/core/stdlib/package.json @@ -1,6 +1,6 @@ { "name": "@robonen/stdlib", - "version": "0.0.11", + "version": "0.0.12", "license": "Apache-2.0", "description": "A collection of tools, utilities, and helpers for TypeScript", "keywords": [ diff --git a/core/stdlib/src/patterns/behavioral/StateMachine/index.test-d.ts b/core/stdlib/src/patterns/behavioral/StateMachine/index.test-d.ts index 361aec2..0b3923d 100644 --- a/core/stdlib/src/patterns/behavioral/StateMachine/index.test-d.ts +++ b/core/stdlib/src/patterns/behavioral/StateMachine/index.test-d.ts @@ -21,4 +21,28 @@ describe('createMachine', () => { it('send returns the (typed) resulting state', () => { expectTypeOf(machine.send('START')).toEqualTypeOf<'idle' | 'running'>(); }); + + it('empty terminal nodes do not widen the event union to string', () => { + const terminal = createMachine({ + initial: 'idle', + states: { + idle: { on: { START: 'done' } }, + done: {}, + }, + }); + + expectTypeOf(terminal.send).parameter(0).toEqualTypeOf<'START'>(); + }); + + it('entry/exit-only nodes do not widen the event union either', () => { + const hooked = createMachine({ + initial: 'idle', + states: { + idle: { on: { START: 'done' } }, + done: { entry: () => {} }, + }, + }); + + expectTypeOf(hooked.send).parameter(0).toEqualTypeOf<'START'>(); + }); }); diff --git a/core/stdlib/src/patterns/behavioral/StateMachine/index.test.ts b/core/stdlib/src/patterns/behavioral/StateMachine/index.test.ts index 4fcdffd..66a80ac 100644 --- a/core/stdlib/src/patterns/behavioral/StateMachine/index.test.ts +++ b/core/stdlib/src/patterns/behavioral/StateMachine/index.test.ts @@ -418,6 +418,7 @@ describe('asyncStateMachine', () => { }, }); + // @ts-expect-error -- deliberately undeclared event: runtime must ignore it const result = await machine.send('STOP'); expect(result).toBe('idle'); @@ -597,6 +598,7 @@ describe('asyncStateMachine', () => { }, }); + // @ts-expect-error -- deliberately undeclared event: can() must report false expect(await machine.can('STOP')).toBe(false); }); diff --git a/core/stdlib/src/patterns/behavioral/StateMachine/types.ts b/core/stdlib/src/patterns/behavioral/StateMachine/types.ts index a4ef161..5f376ff 100644 --- a/core/stdlib/src/patterns/behavioral/StateMachine/types.ts +++ b/core/stdlib/src/patterns/behavioral/StateMachine/types.ts @@ -57,8 +57,12 @@ export type AsyncStateNodeConfig = StateNodeConfig = keyof T & string; +// `on` is matched as REQUIRED here on purpose: an empty terminal node (`{}`) +// satisfies an optional-`on` pattern with no inference candidate, so `infer E` +// would fall back to its constraint and collapse the whole union to `string`, +// silently accepting any event name in `send`/`can`. export type ExtractEvents = { - [K in keyof T]: T[K] extends { readonly on?: Readonly> } + [K in keyof T]: T[K] extends { readonly on: Readonly> } ? E : never; }[keyof T]; diff --git a/vue/primitives/src/overlays/drawer/DrawerClose.vue b/vue/primitives/src/overlays/drawer/DrawerClose.vue new file mode 100644 index 0000000..3696e89 --- /dev/null +++ b/vue/primitives/src/overlays/drawer/DrawerClose.vue @@ -0,0 +1,25 @@ + + + + + diff --git a/vue/primitives/src/overlays/drawer/DrawerContent.vue b/vue/primitives/src/overlays/drawer/DrawerContent.vue index 193a0e9..afacdfb 100644 --- a/vue/primitives/src/overlays/drawer/DrawerContent.vue +++ b/vue/primitives/src/overlays/drawer/DrawerContent.vue @@ -14,6 +14,7 @@ export type DrawerContentEmits = DialogContentEmits;