diff --git a/packages/nanoviews/src/elements/autoFocus.ts b/packages/nanoviews/src/elements/autoFocus.ts index abbca2b5..7c934b80 100644 --- a/packages/nanoviews/src/elements/autoFocus.ts +++ b/packages/nanoviews/src/elements/autoFocus.ts @@ -20,7 +20,7 @@ export const autoFocus$ = /* @__PURE__ */ createEffectAttribute<'autoFocus$', HT ) declare module 'nanoviews' { - interface EffectAttributeValues { + interface EffectAttributeValues { autoFocus$: ValueOrAccessor } diff --git a/packages/nanoviews/src/elements/classList.ts b/packages/nanoviews/src/elements/classList.ts index 59c5889d..03b1fa2c 100644 --- a/packages/nanoviews/src/elements/classList.ts +++ b/packages/nanoviews/src/elements/classList.ts @@ -39,7 +39,7 @@ export const classList$ = /* @__PURE__ */ createEffectAttribute<'classList$', HT ) declare module 'nanoviews' { - interface EffectAttributeValues { + interface EffectAttributeValues { classList$: ClassList } diff --git a/packages/nanoviews/src/elements/controls.ts b/packages/nanoviews/src/elements/controls.ts index b178e40b..d2b1b209 100644 --- a/packages/nanoviews/src/elements/controls.ts +++ b/packages/nanoviews/src/elements/controls.ts @@ -183,7 +183,7 @@ export const files$ = /* @__PURE__ */ createEffectAttribute<'files$', FileElemen ) declare module 'nanoviews' { - interface EffectAttributeValues { + interface EffectAttributeValues { value$: Value checked$: Checked selected$: Selected diff --git a/packages/nanoviews/src/elements/ref.spec.ts b/packages/nanoviews/src/elements/ref.spec.ts index 975a958d..6c44060b 100644 --- a/packages/nanoviews/src/elements/ref.spec.ts +++ b/packages/nanoviews/src/elements/ref.spec.ts @@ -1,11 +1,15 @@ import { describe, it, - expect + expect, + expectTypeOf } from 'vitest' import { render } from '@nanoviews/testing-library' import { signal } from 'kida' -import { button } from './elements.js' +import { + button, + input +} from './elements.js' import { ref$ } from './ref.js' describe('nanoviews', () => { @@ -20,6 +24,32 @@ describe('nanoviews', () => { expect(ref()).toBeInstanceOf(HTMLButtonElement) }) + + it('should take a signal of the element it sits on', () => { + const ref = signal(null) + + render(() => button({ + [ref$]: ref + })('Click me!')) + + // the point of the precise type: no cast to reach the element's own + // surface + expectTypeOf(ref()).toEqualTypeOf() + expect(ref()!.type).toBe('submit') + }) + + it('should reject a signal of a different element', () => { + const ref = signal(null) + + button({ + // @ts-expect-error the button is not an input + [ref$]: ref + })('Click me!') + + input({ + [ref$]: ref + }) + }) }) }) }) diff --git a/packages/nanoviews/src/elements/ref.ts b/packages/nanoviews/src/elements/ref.ts index f6f223b1..69ea2ab2 100644 --- a/packages/nanoviews/src/elements/ref.ts +++ b/packages/nanoviews/src/elements/ref.ts @@ -17,8 +17,14 @@ export const ref$ = /* @__PURE__ */ createEffectAttribute<'ref$', Element, Writa ) declare module 'nanoviews' { - interface EffectAttributeValues { - ref$: WritableSignal + interface EffectAttributeValues { + // A signal is invariant, so every type a ref may be declared with has to + // be named: the element itself, the two branches of the tree it belongs + // to, and their root + ref$: WritableSignal + | WritableSignal + | WritableSignal + | WritableSignal } interface EffectAttributeTargets { diff --git a/packages/nanoviews/src/elements/style.ts b/packages/nanoviews/src/elements/style.ts index 81d9211e..dbf8962b 100644 --- a/packages/nanoviews/src/elements/style.ts +++ b/packages/nanoviews/src/elements/style.ts @@ -72,7 +72,7 @@ export const style$ = /* @__PURE__ */ createEffectAttribute<'style$', HTMLElemen ) declare module 'nanoviews' { - interface EffectAttributeValues { + interface EffectAttributeValues { style$: StyleProps } diff --git a/packages/nanoviews/src/internals/types/effectAttribute.ts b/packages/nanoviews/src/internals/types/effectAttribute.ts index a042c530..6064007c 100644 --- a/packages/nanoviews/src/internals/types/effectAttribute.ts +++ b/packages/nanoviews/src/internals/types/effectAttribute.ts @@ -1,20 +1,26 @@ import type { UnknownAttributes } from './attributes.js' declare module 'nanoviews' { - interface EffectAttributeValues { + // The value an attribute takes may be about the element it sits on, so the + // map is asked with the target and answers for it. An attribute that does + // not care simply never mentions it + interface EffectAttributeValues { } interface EffectAttributeTargets { } } -export type GetEffectAttributeValue = T extends keyof import('nanoviews').EffectAttributeValues - ? import('nanoviews').EffectAttributeValues[T] +export type GetEffectAttributeValue< + T extends string, + Target extends Element +> = T extends keyof import('nanoviews').EffectAttributeValues + ? import('nanoviews').EffectAttributeValues[T] : never export type PickEffectAttributesByTarget = { [K in keyof import('nanoviews').EffectAttributeTargets]?: Target extends import('nanoviews').EffectAttributeTargets[K] - ? GetEffectAttributeValue + ? GetEffectAttributeValue : never } diff --git a/todo.txt b/todo.txt index 219b7a0e..31e9e3cc 100644 --- a/todo.txt +++ b/todo.txt @@ -47,7 +47,6 @@ - for_ duplicate track keys: unsupported by design, must keep failing loudly - the crash lands a step away from the cause, so it needs a clear throw at the point of detection - rework return slot$ to look like element/component? fn.prop is faster than {f,p} - util to transform static props to signal props? -- typed effectattrs - additional arg for record in for$? as$ - static index for untracked loop?