Rename static attributes map to attributeFor and reserve the name
This commit is contained in:
parent
c961d01fc4
commit
6efcdd210e
17
src/types.ts
17
src/types.ts
@ -181,8 +181,8 @@ export type WebComponentConstructor<S> = Omit<BaseOf<S>, 'prototype'> & {
|
||||
new (): WebComponentInstance<S>;
|
||||
readonly prototype: WebComponentInstance<S>;
|
||||
readonly observedAttributes: readonly ObservedAttributes<S>[];
|
||||
/** Prop key → attribute name. */
|
||||
readonly attributes: AttributeMap<S>;
|
||||
/** Prop key → attribute name: `El.attributeFor.fetch` is `'disable-fetch'`. */
|
||||
readonly attributeFor: AttributeMap<S>;
|
||||
readonly [spec]?: S;
|
||||
};
|
||||
|
||||
@ -202,19 +202,26 @@ export type AttributesOf<T> = ObservedAttributes<SpecOf<T>>;
|
||||
/** Mixin members a prop may not shadow. */
|
||||
type MixinMember = keyof WebComponentMembers<unknown>;
|
||||
|
||||
/**
|
||||
* Every name a prop may not use. `attributeFor` is static, so it wouldn't
|
||||
* collide at runtime; it's reserved so `El.attributeFor` and a prop can't be
|
||||
* confused.
|
||||
*/
|
||||
type ReservedName = MixinMember | 'attributeFor';
|
||||
|
||||
/** Branded error type: shows up in the message when a prop name collides. */
|
||||
export type PropNameConflict<K extends PropertyKey> =
|
||||
`Error: prop "${K & string}" conflicts with an existing element member`;
|
||||
|
||||
/**
|
||||
* Intersected with the spec parameter. Any prop whose name already exists on
|
||||
* the base instance (`name`, `title`, `hidden` on `HTMLElement`; `value` on a
|
||||
* form-ish base) or on the mixin itself is retyped to {@link PropNameConflict},
|
||||
* the base instance (`title`, `hidden`, `id` on `HTMLElement`; `value` on a
|
||||
* form-ish base), on the mixin, or is otherwise reserved, is retyped to {@link PropNameConflict},
|
||||
* so the error lands on that key.
|
||||
*/
|
||||
export type ValidateSpec<S> = {
|
||||
readonly props?: {
|
||||
readonly [K in keyof PropsOf<S>]: K extends keyof InstanceType<BaseOf<S>> | MixinMember
|
||||
readonly [K in keyof PropsOf<S>]: K extends keyof InstanceType<BaseOf<S>> | ReservedName
|
||||
? PropNameConflict<K>
|
||||
: PropsOf<S>[K];
|
||||
};
|
||||
|
||||
@ -61,7 +61,7 @@ export function WebComponent<const S extends Spec>(
|
||||
|
||||
class WebComponentElement extends Base {
|
||||
static readonly observedAttributes = resolved.map((prop) => prop.attribute);
|
||||
static readonly attributes = Object.fromEntries(
|
||||
static readonly attributeFor = Object.fromEntries(
|
||||
resolved.map((prop) => [prop.key, prop.attribute]),
|
||||
);
|
||||
|
||||
|
||||
@ -56,9 +56,9 @@ describe('attributes', () => {
|
||||
expectTypeOf<AttributesOf<typeof SPEC>>().toEqualTypeOf<Expected>();
|
||||
});
|
||||
|
||||
test('attributes maps each key to its attribute', () => {
|
||||
expectTypeOf(Example.attributes.fetch).toEqualTypeOf<'disable-fetch'>();
|
||||
expectTypeOf(Example.attributes.ready).toEqualTypeOf<'is-ready'>();
|
||||
test('attributeFor maps each key to its attribute', () => {
|
||||
expectTypeOf(Example.attributeFor.fetch).toEqualTypeOf<'disable-fetch'>();
|
||||
expectTypeOf(Example.attributeFor.ready).toEqualTypeOf<'is-ready'>();
|
||||
});
|
||||
|
||||
test('dataPrefix applies only to derived names', () => {
|
||||
@ -157,6 +157,8 @@ describe('conflicts', () => {
|
||||
test('prop names colliding with mixin members are rejected', () => {
|
||||
// @ts-expect-error -- `signal` is a mixin member
|
||||
WebComponent({ props: { signal: string() } });
|
||||
// @ts-expect-error -- `attributeFor` is reserved for the static lookup
|
||||
WebComponent({ props: { attributeFor: string() } });
|
||||
});
|
||||
|
||||
test('conflicts are checked against the base class', () => {
|
||||
|
||||
@ -109,7 +109,7 @@ describe('props', () => {
|
||||
props: { userId: number(), fetch: disable(), ready: enable({ attribute: 'is-ready' }) },
|
||||
}) {}
|
||||
expect(El.observedAttributes).toEqual(['data-user-id', 'data-disable-fetch', 'is-ready']);
|
||||
expect(El.attributes).toEqual({
|
||||
expect(El.attributeFor).toEqual({
|
||||
userId: 'data-user-id',
|
||||
fetch: 'data-disable-fetch',
|
||||
ready: 'is-ready',
|
||||
|
||||
Loading…
Reference in New Issue
Block a user