praxis-kit 7.0.0 → 7.4.0

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.
@@ -2,9 +2,72 @@ import { RequireAtLeastOne, Simplify, ReadonlyDeep } from 'type-fest';
2
2
  import { Diagnostics, DiagnosticInput, DiagnosticsMode } from '../_shared/diagnostics.js';
3
3
  import { LitElement } from 'lit';
4
4
 
5
+ /**
6
+ * A string-keyed object whose values are of type `T`.
7
+ */
5
8
  type StringMap<T = unknown> = Record<string, T>;
9
+ /**
10
+ * A string-keyed object with values of unknown type.
11
+ */
6
12
  type AnyRecord = StringMap<unknown>;
13
+ /**
14
+ * An object type with no named properties.
15
+ *
16
+ * Unlike `{}`, this excludes arbitrary properties during type operations while
17
+ * still satisfying `extends object`.
18
+ */
7
19
  type EmptyRecord = Record<never, never>;
20
+ /**
21
+ * A compound component's named sub-components, for example
22
+ * `{ Header, Content, Footer }`.
23
+ */
24
+ type SubComponentMap = Readonly<AnyRecord>;
25
+ /**
26
+ * Default `Variants` type for components that declare no variants.
27
+ *
28
+ * Structurally identical to `Readonly<EmptyRecord>`, but named separately so
29
+ * editor hovers remain self-descriptive.
30
+ */
31
+ type NoVariants = Readonly<EmptyRecord>;
32
+ /**
33
+ * Default `TPreset` type for components that declare no named presets.
34
+ *
35
+ * Structurally identical to `Readonly<EmptyRecord>`, but named separately so
36
+ * editor hovers remain self-descriptive.
37
+ */
38
+ type NoPreset = Readonly<EmptyRecord>;
39
+ /**
40
+ * Fallback for `ExtractPluginProps<TPlugin>` when a plugin contributes no
41
+ * props, including the no-plugin case.
42
+ *
43
+ * Structurally identical to `EmptyRecord`, but named separately so editor
44
+ * hovers remain self-descriptive.
45
+ */
46
+ type NoPluginProps = EmptyRecord;
47
+ /**
48
+ * Determines whether an object type should be treated as empty.
49
+ *
50
+ * `keyof T` ignores call and construct signatures...
51
+ */
52
+ type IsEmptyRecord<T extends object> = T extends (...args: never[]) => unknown ? false : T extends new (...args: never[]) => unknown ? false : keyof T extends never ? true : false;
53
+ /**
54
+ * Merges two object types while eliding empty operands.
55
+ *
56
+ * If either operand is {@link EmptyRecord}, the other operand is returned
57
+ * directly instead of producing intersections such as
58
+ * `Component & EmptyRecord` in editor hovers.
59
+ *
60
+ * Unlike a homomorphic mapped type (for example `Simplify<T>`), this preserves
61
+ * call and construct signatures. Many component types are callable objects,
62
+ * and mapped types silently discard those signatures.
63
+ *
64
+ * @remarks
65
+ * Instantiate `MergeRecords` directly. Introducing an intermediate alias for
66
+ * one operand (for example `type C = PolymorphicComponent<G>`) can prevent
67
+ * `IsEmptyRecord` from evaluating eagerly, which breaks assignability under
68
+ * `exactOptionalPropertyTypes`.
69
+ */
70
+ type MergeRecords<A extends object, B extends object> = IsEmptyRecord<A> extends true ? B : IsEmptyRecord<B> extends true ? A : A & B;
8
71
 
9
72
  type IntrinsicTag = keyof HTMLElementTagNameMap;
10
73
 
@@ -170,7 +233,7 @@ type ClassPluginFactory<TProps extends AnyRecord = EmptyRecord> = <V extends Var
170
233
  * wherever a factory's concrete plugin-props shape isn't tracked (factory generics,
171
234
  * capability wiring). */
172
235
  type AnyClassPluginFactory = ClassPluginFactory<AnyRecord> | undefined;
173
- type ExtractPluginProps<TPlugin extends AnyClassPluginFactory> = TPlugin extends ClassPluginFactory<infer T> ? string extends keyof T ? EmptyRecord : T : EmptyRecord;
236
+ type ExtractPluginProps<TPlugin extends AnyClassPluginFactory> = TPlugin extends ClassPluginFactory<infer T> ? string extends keyof T ? NoPluginProps : T : NoPluginProps;
174
237
 
175
238
  type AriaContext = {
176
239
  readonly tag: IntrinsicTag;
@@ -234,6 +297,12 @@ type EnforcementOptions<TAllowed extends ElementType = ElementType> = {
234
297
  * `@praxis-kit/diagnostics`.
235
298
  */
236
299
  readonly diagnostics?: Diagnostics | DiagnosticsMode;
300
+ /**
301
+ * ARIA/accessibility rules evaluated against the resolved tag and props on every render.
302
+ * Each rule is a function receiving the current context and returning zero or more
303
+ * violations, some of which can carry an auto-applicable fix (see `createRemoveAttributeRule`
304
+ * and friends in `praxis-kit/contract`).
305
+ */
237
306
  readonly aria?: readonly AriaRule[];
238
307
  /**
239
308
  * Rules that need `AriaPolicyEngine`'s fix-application/caching machinery
@@ -245,6 +314,11 @@ type EnforcementOptions<TAllowed extends ElementType = ElementType> = {
245
314
  * misleading `aria` name to get the machinery it needs.
246
315
  */
247
316
  readonly rules?: readonly AriaRule[];
317
+ /**
318
+ * Declares which children are valid, by name, match predicate, and cardinality (e.g. "at
319
+ * least 1, at most 4 `Button` children"). Open by default — children matching no rule are
320
+ * still allowed unless `exclusiveChildren` is set.
321
+ */
248
322
  readonly children?: readonly ChildRuleInput[];
249
323
  /**
250
324
  * When true, only children matching a `children` rule (or text, per `allowText`)
@@ -257,19 +331,49 @@ type EnforcementOptions<TAllowed extends ElementType = ElementType> = {
257
331
  * or any listed rule. Default: true.
258
332
  */
259
333
  readonly allowText?: boolean;
334
+ /**
335
+ * Prop transforms composed with the component's own `normalize` (from `FactoryOptions`) and
336
+ * run before it. Unlike `normalize`, these live in the enforcement bucket because they
337
+ * typically encode a built-in HTML/ARIA fact rather than component-specific behavior.
338
+ */
260
339
  readonly props?: readonly PropNormalizer[];
261
340
  /** Restricts the `as` prop to this set of tags. Violations route through diagnostics. */
262
341
  readonly allowedAs?: readonly TAllowed[];
263
342
  };
264
343
 
265
344
  type StylingOptions<V extends Readonly<VariantMap> = Readonly<EmptyRecord>, TPreset extends RecipeMap<V> = Readonly<EmptyRecord>, TPlugin extends AnyClassPluginFactory = AnyClassPluginFactory> = {
345
+ /** Class applied to every instance regardless of variant selection. */
266
346
  readonly base?: ClassName;
347
+ /**
348
+ * Named variant groups (e.g. `intent`, `size`), each mapping its possible values to a
349
+ * class string. A consumer selects a value per group as a prop (`<Button intent="primary">`).
350
+ */
267
351
  readonly variants?: V;
352
+ /** Value used for a variant group when the consumer doesn't pass one explicitly. */
268
353
  readonly defaults?: Partial<DefaultVariants<V>>;
354
+ /**
355
+ * Applies an extra class only when a specific *combination* of variant selections matches —
356
+ * for cases `variants` alone can't express (e.g. `intent: 'primary'` + `size: 'lg'` together
357
+ * need a class neither variant would add on its own).
358
+ */
269
359
  readonly compounds?: readonly CompoundVariant<V>[];
360
+ /**
361
+ * Named bundles of variant values, selectable as a single unit via the `recipe` prop (e.g.
362
+ * `<Button recipe="cta">` instead of setting `intent`/`size` individually).
363
+ */
270
364
  readonly presets?: TPreset;
365
+ /** Maps a resolved tag directly to a raw class string, independent of the variant system. */
271
366
  readonly tags?: Readonly<TagMap>;
367
+ /**
368
+ * A `ClassPluginFactory` (e.g. the Tailwind layout pipeline) that extends class resolution
369
+ * with its own owned props, layered on top of `variants`/`presets`/`tags`.
370
+ */
272
371
  readonly plugin?: TPlugin;
372
+ /**
373
+ * A cache-key → resolved-class-string lookup for every statically-known variant
374
+ * combination, skipping runtime class computation entirely when a match is found. Normally
375
+ * generated by a build-time class-extraction plugin rather than hand-authored.
376
+ */
273
377
  readonly precomputedClasses?: Readonly<Record<string, string>>;
274
378
  };
275
379
 
@@ -278,19 +382,65 @@ type NormalizeFn<Props extends AnyRecord = AnyRecord> = {
278
382
  }['normalize'];
279
383
  type AnyFactoryOptions = FactoryOptions<ElementType, AnyRecord, VariantMap, RecipeMap<VariantMap>, AnyClassPluginFactory>;
280
384
  type FactoryOptions<TDefault extends ElementType = ElementType, Props extends AnyRecord = EmptyRecord, V extends Readonly<VariantMap> = Readonly<EmptyRecord>, TPreset extends RecipeMap<V> = Readonly<EmptyRecord>, TPlugin extends AnyClassPluginFactory = AnyClassPluginFactory, TAllowed extends ElementType = ElementType> = {
385
+ /** The intrinsic tag the component renders by default. Overridable per instance via `as`. */
281
386
  readonly tag?: TDefault;
387
+ /** Display name used in diagnostics, dev tools, and generated component naming. */
282
388
  readonly name?: string;
389
+ /** Values used for the component's own (non-variant) props when the consumer omits them. */
283
390
  readonly defaults?: Partial<NoInfer<Props>>;
391
+ /**
392
+ * A pure `(props) => props` transform run on every render, after `enforcement.props`'s
393
+ * normalizers see the same input. Use this for component-specific prop shaping — anything
394
+ * that depends on live instance state or the real DOM element belongs in `onElement` instead.
395
+ */
284
396
  readonly normalize?: NormalizeFn<NoInfer<Props>>;
397
+ /** Variant groups, base classes, presets, and the optional class-resolution plugin. */
285
398
  readonly styling?: StylingOptions<V, TPreset, TPlugin>;
399
+ /** ARIA rules, child-content contracts, and other runtime validation for this component. */
286
400
  readonly enforcement?: EnforcementOptions<TAllowed>;
287
401
  /**
288
402
  * Adapter-resolved diagnostics default, spread in by `resolveAdapterCommonOptions`. Not meant to
289
403
  * be set directly by component authors — use `enforcement.diagnostics` to override per component.
290
404
  */
291
405
  readonly diagnostics?: Diagnostics;
406
+ /**
407
+ * Sub-components to attach to the generated root component, producing a
408
+ * compound component API (for example, `Card.Header`, `Card.Content`,
409
+ * and `Card.Footer`). Purely additive — has no effect on
410
+ * `enforcement.children`; author child rules explicitly if the component
411
+ * needs to validate its children.
412
+ */
413
+ readonly subComponents?: SubComponentMap;
414
+ /**
415
+ * Called once per instance, when the real underlying DOM element first
416
+ * exists, in every adapter — via that adapter's own native mount
417
+ * lifecycle, never through the props/attribute pipeline. Use this for
418
+ * wiring that needs the actual element (native imperative methods like
419
+ * `dialogEl.showModal()`, native events like `close`/`cancel` that have
420
+ * no prop-based equivalent), not for anything expressible as a plain
421
+ * prop.
422
+ *
423
+ * `getProps` returns the instance's *current* resolved props at call
424
+ * time — read it from inside a listener registered once at mount, rather
425
+ * than re-subscribing on every prop change.
426
+ *
427
+ * Return a cleanup function to run when the instance unmounts.
428
+ */
429
+ readonly onElement?: (element: Element, getProps: () => Readonly<Props>) => void | (() => void);
292
430
  };
293
431
 
432
+ /**
433
+ * Determines whether a prop should be stripped before forwarding to the
434
+ * rendered element.
435
+ *
436
+ * Returning `true` excludes the prop from the output; returning `false`
437
+ * keeps it. This is the inverse polarity of `shouldForwardProp`-style
438
+ * predicates (Emotion/styled-components), where `true` means include.
439
+ *
440
+ * @param key - The prop name being evaluated.
441
+ * @param variantKeys - The set of configured variant prop names.
442
+ * @returns `true` to strip the prop; `false` to forward it.
443
+ */
294
444
  type FilterPredicate = (key: string, variantKeys: ReadonlySet<string>) => boolean;
295
445
 
296
446
  declare function defineContractComponent<O extends FactoryOptions>(options: O): <R>(factory: (options: O) => R) => R;
@@ -306,7 +456,7 @@ declare function defineContractComponent<O extends FactoryOptions>(options: O):
306
456
  * Note: this adapter targets Light DOM composition only. Shadow DOM slot
307
457
  * protocol is intentionally out of scope.
308
458
  */
309
- type LitFactoryOptions<TDefault extends ElementType = ElementType, TProps extends AnyRecord = EmptyRecord, TVariants extends Readonly<VariantMap> = Readonly<EmptyRecord>, TPreset extends RecipeMap<TVariants> = Readonly<EmptyRecord>, TPlugin extends AnyClassPluginFactory = AnyClassPluginFactory> = FactoryOptions<TDefault, TProps, TVariants, TPreset, TPlugin> & {
459
+ type LitFactoryOptions<TDefault extends ElementType = ElementType, TProps extends AnyRecord = EmptyRecord, TVariants extends Readonly<VariantMap> = NoVariants, TPreset extends RecipeMap<TVariants> = NoPreset, TPlugin extends AnyClassPluginFactory = AnyClassPluginFactory> = FactoryOptions<TDefault, TProps, TVariants, TPreset, TPlugin> & {
310
460
  readonly filterProps?: FilterPredicate;
311
461
  };
312
462
 
@@ -318,14 +468,14 @@ type UnknownProps = AnyRecord;
318
468
  * (which would trigger TS4094 in declaration emit). Variant key instance
319
469
  * properties are typed via the TVariants parameter.
320
470
  */
321
- type LitContractComponent<TVariants extends Readonly<VariantMap> = Readonly<EmptyRecord>, TPluginProps extends AnyRecord = EmptyRecord> = {
322
- new (): LitElement & {
471
+ type LitContractComponent<TVariants extends Readonly<VariantMap> = NoVariants, TPluginProps extends AnyRecord = EmptyRecord> = {
472
+ new (): MergeRecords<LitElement & {
323
473
  as: string | undefined;
324
474
  recipe: string | undefined;
325
475
  praxisClass: string | undefined;
326
476
  } & {
327
477
  [K in Extract<keyof TVariants, string>]?: string | null;
328
- } & TPluginProps;
478
+ }, TPluginProps>;
329
479
  };
330
480
 
331
481
  /**
@@ -348,7 +498,9 @@ type LitContractComponent<TVariants extends Readonly<VariantMap> = Readonly<Empt
348
498
  * customElements.define('praxis-button', Button)
349
499
  * ```
350
500
  */
351
- declare function createContractComponent<TDefault extends ElementType, TProps extends UnknownProps = EmptyRecord, TVariants extends Readonly<VariantMap> = Readonly<EmptyRecord>, TPreset extends RecipeMap<TVariants> = Readonly<EmptyRecord>, TPlugin extends AnyClassPluginFactory = AnyClassPluginFactory>(options: LitFactoryOptions<TDefault, TProps, TVariants, TPreset, TPlugin>): LitContractComponent<TVariants, ExtractPluginProps<TPlugin>>;
501
+ declare function createContractComponent<TDefault extends ElementType, TProps extends UnknownProps = EmptyRecord, TVariants extends Readonly<VariantMap> = NoVariants, TPreset extends RecipeMap<TVariants> = NoPreset, TPlugin extends AnyClassPluginFactory = AnyClassPluginFactory, TSubComponents extends Readonly<AnyRecord> = EmptyRecord>(options: LitFactoryOptions<TDefault, TProps, TVariants, TPreset, TPlugin> & {
502
+ readonly subComponents?: TSubComponents;
503
+ }): MergeRecords<LitContractComponent<TVariants, ExtractPluginProps<TPlugin>>, TSubComponents>;
352
504
 
353
505
  /**
354
506
  * Renders a praxis-kit Lit component to an HTML string without requiring a DOM.
package/dist/lit/index.js CHANGED
@@ -1,8 +1,22 @@
1
+ // ../../lib/adapter-utils/src/invariant.ts
2
+ function panic(message) {
3
+ throw new Error(message);
4
+ }
5
+ function invariant(condition, message) {
6
+ if (!condition) panic(message);
7
+ }
8
+
1
9
  // ../../lib/adapter-utils/src/runtime/define-component.ts
2
10
  function defineContractComponent(options) {
3
11
  return (factory) => factory(options);
4
12
  }
5
13
 
14
+ // ../../lib/adapter-utils/src/runtime/assemble-compound-component.ts
15
+ function assembleCompoundComponent(root, subComponents) {
16
+ if (!subComponents) return root;
17
+ return Object.assign(root, subComponents);
18
+ }
19
+
6
20
  // ../../lib/primitive/src/tag/resolve-tag.ts
7
21
  function makeResolveTag(defaultTag) {
8
22
  return function tag(as) {
@@ -45,10 +59,13 @@ function isString(value) {
45
59
  function isNumber(value) {
46
60
  return typeof value === "number";
47
61
  }
62
+ function isFunction(value) {
63
+ return typeof value === "function";
64
+ }
48
65
 
49
66
  // ../../lib/primitive/src/rule/is-dynamic-rule.ts
50
67
  function isDynamicRule(rule) {
51
- return isObject(rule, true) && rule[RULE_BRAND] === true;
68
+ return isObject(rule, true) && Reflect.get(rule, RULE_BRAND) === true;
52
69
  }
53
70
 
54
71
  // ../../lib/primitive/src/rule/resolve-rule.ts
@@ -587,17 +604,17 @@ var COMPONENT_DEFAULT_TAG = /* @__PURE__ */ Symbol.for("praxis.component-default
587
604
  // ../../lib/primitive/src/guards/children/is-tag.ts
588
605
  function getAsProp(child) {
589
606
  if (!isObject(child) || !("props" in child)) return void 0;
590
- const props = child.props;
607
+ const { props } = child;
591
608
  if (!isObject(props)) return void 0;
592
- const as = props.as;
609
+ const as = Reflect.get(props, "as");
593
610
  return isString(as) && as !== "" ? as : void 0;
594
611
  }
595
612
  function getTag(child) {
596
613
  if (!isObject(child) || !("type" in child)) return void 0;
597
- const t = child.type;
614
+ const { type: t } = child;
598
615
  if (isString(t)) return t;
599
616
  if (typeof t === "function" || isObject(t)) {
600
- const defaultTag = t[COMPONENT_DEFAULT_TAG];
617
+ const defaultTag = Reflect.get(t, COMPONENT_DEFAULT_TAG);
601
618
  if (!isString(defaultTag)) return void 0;
602
619
  return getAsProp(child) ?? defaultTag;
603
620
  }
@@ -617,6 +634,28 @@ function isTag(...args) {
617
634
  return tag !== void 0 && set2.has(tag);
618
635
  }
619
636
 
637
+ // ../../lib/adapter-utils/src/runtime/is-factory-options-like.ts
638
+ var FACTORY_OPTIONS_FIELD_VALIDATORS = {
639
+ tag: (v) => v === void 0 || isString(v),
640
+ name: (v) => v === void 0 || isString(v),
641
+ defaults: (v) => v === void 0 || isObject(v),
642
+ normalize: (v) => v === void 0 || isFunction(v),
643
+ styling: (v) => v === void 0 || isObject(v),
644
+ enforcement: (v) => v === void 0 || isObject(v),
645
+ diagnostics: (v) => v === void 0 || isObject(v),
646
+ subComponents: (v) => v === void 0 || isObject(v),
647
+ onElement: (v) => v === void 0 || isFunction(v)
648
+ };
649
+ function isFactoryOptionsLike(options, extraFieldValidators) {
650
+ if (!isObject(options)) return false;
651
+ const validators = { ...FACTORY_OPTIONS_FIELD_VALIDATORS, ...extraFieldValidators };
652
+ for (const [key, value] of Object.entries(options)) {
653
+ const validate = validators[key];
654
+ if (!validate || !validate(value)) return false;
655
+ }
656
+ return true;
657
+ }
658
+
620
659
  // ../../lib/contract/src/aria/aria-role-policy.ts
621
660
  function getImplicitRole(tag, props) {
622
661
  if (tag in IMPLICIT_ROLE_RECORD) return IMPLICIT_ROLE_RECORD[tag];
@@ -2721,7 +2760,7 @@ function getChildProp(child, key) {
2721
2760
  if (!isVNodeLike(child)) return void 0;
2722
2761
  const { props } = child;
2723
2762
  if (!isObject(props)) return void 0;
2724
- return props[key];
2763
+ return Reflect.get(props, key);
2725
2764
  }
2726
2765
 
2727
2766
  // ../core/src/html/contracts/aria/landmarks.ts
@@ -3503,7 +3542,7 @@ function diffAndApplyAttributes(host, state, prevPipelineAttrs, incomingProps) {
3503
3542
  }
3504
3543
 
3505
3544
  // ../../adapters/lit/src/create-contract-component.ts
3506
- import { LitElement, html } from "lit";
3545
+ import { LitElement as LitElement2, html } from "lit";
3507
3546
 
3508
3547
  // ../../adapters/lit/src/build-runtime.ts
3509
3548
  import { silentDiagnostics as silentDiagnostics4 } from "../_shared/diagnostics.js";
@@ -3531,6 +3570,12 @@ function buildRuntime(options) {
3531
3570
  };
3532
3571
  }
3533
3572
 
3573
+ // ../../adapters/lit/src/is-lit-contract-component.ts
3574
+ import { LitElement } from "lit";
3575
+ function isLitContractComponent(value) {
3576
+ return isFunction(value) && value.prototype instanceof LitElement;
3577
+ }
3578
+
3534
3579
  // ../../adapters/lit/src/render-to-string.ts
3535
3580
  var ssrRegistry = /* @__PURE__ */ new WeakMap();
3536
3581
  function registerForSsr(cls, bundle) {
@@ -3547,8 +3592,17 @@ function renderToString(component, props = {}, innerHTML = "") {
3547
3592
  return renderBundleToString(entry.bundle, props, innerHTML);
3548
3593
  }
3549
3594
 
3595
+ // ../../adapters/lit/src/to-lit-factory-options.ts
3596
+ var LIT_FIELD_VALIDATORS = {
3597
+ filterProps: (v) => v === void 0 || isFunction(v)
3598
+ };
3599
+ function isLitFactoryOptions(options) {
3600
+ return isFactoryOptionsLike(options, LIT_FIELD_VALIDATORS);
3601
+ }
3602
+
3550
3603
  // ../../adapters/lit/src/create-contract-component.ts
3551
3604
  function createContractComponent(options) {
3605
+ invariant(isLitFactoryOptions(options), "options is not a valid LitFactoryOptions object");
3552
3606
  const bundle = buildRuntime(options);
3553
3607
  const looseBundle = toLooseBundle(bundle);
3554
3608
  const variantKeys = options.styling?.variants ? Object.keys(options.styling.variants) : [];
@@ -3573,7 +3627,7 @@ function createContractComponent(options) {
3573
3627
  iterate.forEach(pluginKeys, (key) => {
3574
3628
  staticProps[key] = { type: String, attribute: key };
3575
3629
  });
3576
- class PolymorphicLitElement extends LitElement {
3630
+ class PolymorphicLitElement extends LitElement2 {
3577
3631
  // Tracks keys set by the pipeline last render so stale attrs are removed.
3578
3632
  _pipelineAttrs = /* @__PURE__ */ new Set();
3579
3633
  // Starts true so the first update always runs the pipeline regardless of
@@ -3635,6 +3689,21 @@ function createContractComponent(options) {
3635
3689
  const props = this._buildProps();
3636
3690
  diffAndApplyAttributes(this, resolveHostState(looseBundle, props), this._pipelineAttrs, props);
3637
3691
  }
3692
+ // Light DOM means `this` already is the real host element — no ref/indirection needed.
3693
+ // connectedCallback/disconnectedCallback are the native mount/unmount lifecycle, called
3694
+ // once per instance regardless of how many pipeline re-runs `_applyPraxis` does.
3695
+ _onElementCleanup;
3696
+ connectedCallback() {
3697
+ super.connectedCallback();
3698
+ if (options.onElement) {
3699
+ this._onElementCleanup = options.onElement(this, () => this._buildProps()) ?? void 0;
3700
+ }
3701
+ }
3702
+ disconnectedCallback() {
3703
+ super.disconnectedCallback();
3704
+ this._onElementCleanup?.();
3705
+ this._onElementCleanup = void 0;
3706
+ }
3638
3707
  render() {
3639
3708
  const children = Array.from(this.childNodes);
3640
3709
  const { tag, normalizedProps } = resolveTagAndNormalizedProps(looseBundle, this._buildProps());
@@ -3648,8 +3717,13 @@ function createContractComponent(options) {
3648
3717
  if (options.name) {
3649
3718
  Object.defineProperty(PolymorphicLitElement, "name", { value: options.name });
3650
3719
  }
3720
+ invariant(
3721
+ isLitContractComponent(PolymorphicLitElement),
3722
+ "Generated class failed to satisfy the LitContractComponent shape"
3723
+ );
3651
3724
  registerForSsr(PolymorphicLitElement, looseBundle);
3652
- return PolymorphicLitElement;
3725
+ const assembled = assembleCompoundComponent(PolymorphicLitElement, options.subComponents);
3726
+ return assembled;
3653
3727
  }
3654
3728
  export {
3655
3729
  createContractComponent,