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.
@@ -3,9 +3,72 @@ import { Diagnostics, DiagnosticInput, DiagnosticsMode } from '../_shared/diagno
3
3
  import * as vue from 'vue';
4
4
  import { AllowedComponentProps } from 'vue';
5
5
 
6
+ /**
7
+ * A string-keyed object whose values are of type `T`.
8
+ */
6
9
  type StringMap<T = unknown> = Record<string, T>;
10
+ /**
11
+ * A string-keyed object with values of unknown type.
12
+ */
7
13
  type AnyRecord = StringMap<unknown>;
14
+ /**
15
+ * An object type with no named properties.
16
+ *
17
+ * Unlike `{}`, this excludes arbitrary properties during type operations while
18
+ * still satisfying `extends object`.
19
+ */
8
20
  type EmptyRecord = Record<never, never>;
21
+ /**
22
+ * A compound component's named sub-components, for example
23
+ * `{ Header, Content, Footer }`.
24
+ */
25
+ type SubComponentMap = Readonly<AnyRecord>;
26
+ /**
27
+ * Default `Variants` type for components that declare no variants.
28
+ *
29
+ * Structurally identical to `Readonly<EmptyRecord>`, but named separately so
30
+ * editor hovers remain self-descriptive.
31
+ */
32
+ type NoVariants = Readonly<EmptyRecord>;
33
+ /**
34
+ * Default `TPreset` type for components that declare no named presets.
35
+ *
36
+ * Structurally identical to `Readonly<EmptyRecord>`, but named separately so
37
+ * editor hovers remain self-descriptive.
38
+ */
39
+ type NoPreset = Readonly<EmptyRecord>;
40
+ /**
41
+ * Fallback for `ExtractPluginProps<TPlugin>` when a plugin contributes no
42
+ * props, including the no-plugin case.
43
+ *
44
+ * Structurally identical to `EmptyRecord`, but named separately so editor
45
+ * hovers remain self-descriptive.
46
+ */
47
+ type NoPluginProps = EmptyRecord;
48
+ /**
49
+ * Determines whether an object type should be treated as empty.
50
+ *
51
+ * `keyof T` ignores call and construct signatures...
52
+ */
53
+ type IsEmptyRecord<T extends object> = T extends (...args: never[]) => unknown ? false : T extends new (...args: never[]) => unknown ? false : keyof T extends never ? true : false;
54
+ /**
55
+ * Merges two object types while eliding empty operands.
56
+ *
57
+ * If either operand is {@link EmptyRecord}, the other operand is returned
58
+ * directly instead of producing intersections such as
59
+ * `Component & EmptyRecord` in editor hovers.
60
+ *
61
+ * Unlike a homomorphic mapped type (for example `Simplify<T>`), this preserves
62
+ * call and construct signatures. Many component types are callable objects,
63
+ * and mapped types silently discard those signatures.
64
+ *
65
+ * @remarks
66
+ * Instantiate `MergeRecords` directly. Introducing an intermediate alias for
67
+ * one operand (for example `type C = PolymorphicComponent<G>`) can prevent
68
+ * `IsEmptyRecord` from evaluating eagerly, which breaks assignability under
69
+ * `exactOptionalPropertyTypes`.
70
+ */
71
+ type MergeRecords<A extends object, B extends object> = IsEmptyRecord<A> extends true ? B : IsEmptyRecord<B> extends true ? A : A & B;
9
72
 
10
73
  type IntrinsicTag = keyof HTMLElementTagNameMap;
11
74
 
@@ -188,7 +251,7 @@ type ClassPluginFactory<TProps extends AnyRecord = EmptyRecord> = <V extends Var
188
251
  * wherever a factory's concrete plugin-props shape isn't tracked (factory generics,
189
252
  * capability wiring). */
190
253
  type AnyClassPluginFactory = ClassPluginFactory<AnyRecord> | undefined;
191
- type ExtractPluginProps<TPlugin extends AnyClassPluginFactory> = TPlugin extends ClassPluginFactory<infer T> ? string extends keyof T ? EmptyRecord : T : EmptyRecord;
254
+ type ExtractPluginProps<TPlugin extends AnyClassPluginFactory> = TPlugin extends ClassPluginFactory<infer T> ? string extends keyof T ? NoPluginProps : T : NoPluginProps;
192
255
 
193
256
  type AriaContext = {
194
257
  readonly tag: IntrinsicTag;
@@ -252,6 +315,12 @@ type EnforcementOptions<TAllowed extends ElementType = ElementType> = {
252
315
  * `@praxis-kit/diagnostics`.
253
316
  */
254
317
  readonly diagnostics?: Diagnostics | DiagnosticsMode;
318
+ /**
319
+ * ARIA/accessibility rules evaluated against the resolved tag and props on every render.
320
+ * Each rule is a function receiving the current context and returning zero or more
321
+ * violations, some of which can carry an auto-applicable fix (see `createRemoveAttributeRule`
322
+ * and friends in `praxis-kit/contract`).
323
+ */
255
324
  readonly aria?: readonly AriaRule[];
256
325
  /**
257
326
  * Rules that need `AriaPolicyEngine`'s fix-application/caching machinery
@@ -263,6 +332,11 @@ type EnforcementOptions<TAllowed extends ElementType = ElementType> = {
263
332
  * misleading `aria` name to get the machinery it needs.
264
333
  */
265
334
  readonly rules?: readonly AriaRule[];
335
+ /**
336
+ * Declares which children are valid, by name, match predicate, and cardinality (e.g. "at
337
+ * least 1, at most 4 `Button` children"). Open by default — children matching no rule are
338
+ * still allowed unless `exclusiveChildren` is set.
339
+ */
266
340
  readonly children?: readonly ChildRuleInput[];
267
341
  /**
268
342
  * When true, only children matching a `children` rule (or text, per `allowText`)
@@ -275,19 +349,49 @@ type EnforcementOptions<TAllowed extends ElementType = ElementType> = {
275
349
  * or any listed rule. Default: true.
276
350
  */
277
351
  readonly allowText?: boolean;
352
+ /**
353
+ * Prop transforms composed with the component's own `normalize` (from `FactoryOptions`) and
354
+ * run before it. Unlike `normalize`, these live in the enforcement bucket because they
355
+ * typically encode a built-in HTML/ARIA fact rather than component-specific behavior.
356
+ */
278
357
  readonly props?: readonly PropNormalizer[];
279
358
  /** Restricts the `as` prop to this set of tags. Violations route through diagnostics. */
280
359
  readonly allowedAs?: readonly TAllowed[];
281
360
  };
282
361
 
283
362
  type StylingOptions<V extends Readonly<VariantMap> = Readonly<EmptyRecord>, TPreset extends RecipeMap<V> = Readonly<EmptyRecord>, TPlugin extends AnyClassPluginFactory = AnyClassPluginFactory> = {
363
+ /** Class applied to every instance regardless of variant selection. */
284
364
  readonly base?: ClassName;
365
+ /**
366
+ * Named variant groups (e.g. `intent`, `size`), each mapping its possible values to a
367
+ * class string. A consumer selects a value per group as a prop (`<Button intent="primary">`).
368
+ */
285
369
  readonly variants?: V;
370
+ /** Value used for a variant group when the consumer doesn't pass one explicitly. */
286
371
  readonly defaults?: Partial<DefaultVariants<V>>;
372
+ /**
373
+ * Applies an extra class only when a specific *combination* of variant selections matches —
374
+ * for cases `variants` alone can't express (e.g. `intent: 'primary'` + `size: 'lg'` together
375
+ * need a class neither variant would add on its own).
376
+ */
287
377
  readonly compounds?: readonly CompoundVariant<V>[];
378
+ /**
379
+ * Named bundles of variant values, selectable as a single unit via the `recipe` prop (e.g.
380
+ * `<Button recipe="cta">` instead of setting `intent`/`size` individually).
381
+ */
288
382
  readonly presets?: TPreset;
383
+ /** Maps a resolved tag directly to a raw class string, independent of the variant system. */
289
384
  readonly tags?: Readonly<TagMap>;
385
+ /**
386
+ * A `ClassPluginFactory` (e.g. the Tailwind layout pipeline) that extends class resolution
387
+ * with its own owned props, layered on top of `variants`/`presets`/`tags`.
388
+ */
290
389
  readonly plugin?: TPlugin;
390
+ /**
391
+ * A cache-key → resolved-class-string lookup for every statically-known variant
392
+ * combination, skipping runtime class computation entirely when a match is found. Normally
393
+ * generated by a build-time class-extraction plugin rather than hand-authored.
394
+ */
291
395
  readonly precomputedClasses?: Readonly<Record<string, string>>;
292
396
  };
293
397
 
@@ -296,17 +400,51 @@ type NormalizeFn<Props extends AnyRecord = AnyRecord> = {
296
400
  }['normalize'];
297
401
  type AnyFactoryOptions = FactoryOptions<ElementType, AnyRecord, VariantMap, RecipeMap<VariantMap>, AnyClassPluginFactory>;
298
402
  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> = {
403
+ /** The intrinsic tag the component renders by default. Overridable per instance via `as`. */
299
404
  readonly tag?: TDefault;
405
+ /** Display name used in diagnostics, dev tools, and generated component naming. */
300
406
  readonly name?: string;
407
+ /** Values used for the component's own (non-variant) props when the consumer omits them. */
301
408
  readonly defaults?: Partial<NoInfer<Props>>;
409
+ /**
410
+ * A pure `(props) => props` transform run on every render, after `enforcement.props`'s
411
+ * normalizers see the same input. Use this for component-specific prop shaping — anything
412
+ * that depends on live instance state or the real DOM element belongs in `onElement` instead.
413
+ */
302
414
  readonly normalize?: NormalizeFn<NoInfer<Props>>;
415
+ /** Variant groups, base classes, presets, and the optional class-resolution plugin. */
303
416
  readonly styling?: StylingOptions<V, TPreset, TPlugin>;
417
+ /** ARIA rules, child-content contracts, and other runtime validation for this component. */
304
418
  readonly enforcement?: EnforcementOptions<TAllowed>;
305
419
  /**
306
420
  * Adapter-resolved diagnostics default, spread in by `resolveAdapterCommonOptions`. Not meant to
307
421
  * be set directly by component authors — use `enforcement.diagnostics` to override per component.
308
422
  */
309
423
  readonly diagnostics?: Diagnostics;
424
+ /**
425
+ * Sub-components to attach to the generated root component, producing a
426
+ * compound component API (for example, `Card.Header`, `Card.Content`,
427
+ * and `Card.Footer`). Purely additive — has no effect on
428
+ * `enforcement.children`; author child rules explicitly if the component
429
+ * needs to validate its children.
430
+ */
431
+ readonly subComponents?: SubComponentMap;
432
+ /**
433
+ * Called once per instance, when the real underlying DOM element first
434
+ * exists, in every adapter — via that adapter's own native mount
435
+ * lifecycle, never through the props/attribute pipeline. Use this for
436
+ * wiring that needs the actual element (native imperative methods like
437
+ * `dialogEl.showModal()`, native events like `close`/`cancel` that have
438
+ * no prop-based equivalent), not for anything expressible as a plain
439
+ * prop.
440
+ *
441
+ * `getProps` returns the instance's *current* resolved props at call
442
+ * time — read it from inside a listener registered once at mount, rather
443
+ * than re-subscribing on every prop change.
444
+ *
445
+ * Return a cleanup function to run when the instance unmounts.
446
+ */
447
+ readonly onElement?: (element: Element, getProps: () => Readonly<Props>) => void | (() => void);
310
448
  };
311
449
 
312
450
  declare function defineContractComponent<O extends FactoryOptions>(options: O): <R>(factory: (options: O) => R) => R;
@@ -324,7 +462,7 @@ declare const Slottable: vue.DefineComponent<{}, () => vue.VNode<vue.RendererNod
324
462
 
325
463
  type UnknownProps = AnyRecord;
326
464
 
327
- type VueFactoryOptions<TDefault extends ElementType, Props extends UnknownProps, Variants extends Readonly<VariantMap>, TPreset extends RecipeMap<Variants> = Readonly<EmptyRecord>, TPlugin extends AnyClassPluginFactory = AnyClassPluginFactory> = FactoryOptions<TDefault, Props, Variants, TPreset, TPlugin> & {
465
+ type VueFactoryOptions<TDefault extends ElementType, Props extends UnknownProps, Variants extends Readonly<VariantMap>, TPreset extends RecipeMap<Variants> = NoPreset, TPlugin extends AnyClassPluginFactory = AnyClassPluginFactory> = FactoryOptions<TDefault, Props, Variants, TPreset, TPlugin> & {
328
466
  /**
329
467
  * Return true for any prop key that should be consumed but not forwarded to
330
468
  * the DOM. Variant keys are always stripped automatically.
@@ -370,6 +508,31 @@ type PolymorphicComponent<G extends PolymorphicGenerics> = {
370
508
  displayName?: string;
371
509
  };
372
510
 
373
- declare function createContractComponent<TDefault extends ElementType, Props extends UnknownProps = EmptyRecord, Variants extends Readonly<VariantMap> = Readonly<EmptyRecord>, TPreset extends RecipeMap<Variants> = Readonly<EmptyRecord>, TPlugin extends AnyClassPluginFactory = AnyClassPluginFactory>(options: VueFactoryOptions<TDefault, Props, Variants, TPreset, TPlugin>): PolymorphicComponent<PolymorphicGenerics<TDefault, Props & ExtractPluginProps<TPlugin>, Variants, TPreset>>;
511
+ /**
512
+ * Creates a polymorphic Vue component with praxis-kit contracts applied.
513
+ *
514
+ * ```ts
515
+ * const Button = createContractComponent({
516
+ * tag: 'button',
517
+ * name: 'Button',
518
+ * styling: {
519
+ * base: 'btn',
520
+ * variants: { intent: { primary: 'btn--primary', ghost: 'btn--ghost' } },
521
+ * defaults: { intent: 'primary' },
522
+ * },
523
+ * })
524
+ * ```
525
+ *
526
+ * ```vue
527
+ * <Button intent="ghost" as="a" href="/home">Home</Button>
528
+ * ```
529
+ *
530
+ * Pass `subComponents` to attach named sub-components (`Card.Header`) and `onElement` to run
531
+ * setup once the real DOM element exists — both purely additive on top of the generated
532
+ * component.
533
+ */
534
+ declare function createContractComponent<TDefault extends ElementType, Props extends UnknownProps = EmptyRecord, Variants extends Readonly<VariantMap> = NoVariants, TPreset extends RecipeMap<Variants> = NoPreset, TPlugin extends AnyClassPluginFactory = AnyClassPluginFactory, TSubComponents extends Readonly<AnyRecord> = EmptyRecord>(options: VueFactoryOptions<TDefault, Props, Variants, TPreset, TPlugin> & {
535
+ readonly subComponents?: TSubComponents;
536
+ }): MergeRecords<PolymorphicComponent<PolymorphicGenerics<TDefault, MergeRecords<Props, ExtractPluginProps<TPlugin>>, Variants, TPreset>>, TSubComponents>;
374
537
 
375
538
  export { type AnyFactoryOptions, type ElementType, type EmptyRecord, type PolymorphicComponent, type PolymorphicGenerics, type PolymorphicProps, type PolymorphicWithAsChild, Slottable, type SlottableProps, type VueFactoryOptions, createContractComponent, defineContractComponent };
package/dist/vue/index.js CHANGED
@@ -1,6 +1,30 @@
1
1
  // ../../adapters/vue/src/create-contract-component.ts
2
2
  import { computed, defineComponent as defineComponent2 } from "vue";
3
3
 
4
+ // ../../lib/adapter-utils/src/invariant.ts
5
+ function panic(message) {
6
+ throw new Error(message);
7
+ }
8
+ function invariant(condition, message) {
9
+ if (!condition) panic(message);
10
+ }
11
+
12
+ // ../../lib/adapter-utils/src/runtime/apply-display-name.ts
13
+ function applyDisplayName(component, name) {
14
+ Object.assign(component, { displayName: name ?? "PolymorphicComponent" });
15
+ }
16
+
17
+ // ../../lib/adapter-utils/src/runtime/define-component.ts
18
+ function defineContractComponent(options) {
19
+ return (factory) => factory(options);
20
+ }
21
+
22
+ // ../../lib/adapter-utils/src/runtime/assemble-compound-component.ts
23
+ function assembleCompoundComponent(root, subComponents) {
24
+ if (!subComponents) return root;
25
+ return Object.assign(root, subComponents);
26
+ }
27
+
4
28
  // ../../lib/primitive/src/tag/resolve-tag.ts
5
29
  function makeResolveTag(defaultTag) {
6
30
  return function tag(as) {
@@ -43,10 +67,13 @@ function isString(value) {
43
67
  function isNumber(value) {
44
68
  return typeof value === "number";
45
69
  }
70
+ function isFunction(value) {
71
+ return typeof value === "function";
72
+ }
46
73
 
47
74
  // ../../lib/primitive/src/rule/is-dynamic-rule.ts
48
75
  function isDynamicRule(rule) {
49
- return isObject(rule, true) && rule[RULE_BRAND] === true;
76
+ return isObject(rule, true) && Reflect.get(rule, RULE_BRAND) === true;
50
77
  }
51
78
 
52
79
  // ../../lib/primitive/src/rule/resolve-rule.ts
@@ -676,17 +703,17 @@ var COMPONENT_DEFAULT_TAG = /* @__PURE__ */ Symbol.for("praxis.component-default
676
703
  // ../../lib/primitive/src/guards/children/is-tag.ts
677
704
  function getAsProp(child) {
678
705
  if (!isObject(child) || !("props" in child)) return void 0;
679
- const props = child.props;
706
+ const { props } = child;
680
707
  if (!isObject(props)) return void 0;
681
- const as = props.as;
708
+ const as = Reflect.get(props, "as");
682
709
  return isString(as) && as !== "" ? as : void 0;
683
710
  }
684
711
  function getTag(child) {
685
712
  if (!isObject(child) || !("type" in child)) return void 0;
686
- const t = child.type;
713
+ const { type: t } = child;
687
714
  if (isString(t)) return t;
688
715
  if (typeof t === "function" || isObject(t)) {
689
- const defaultTag = t[COMPONENT_DEFAULT_TAG];
716
+ const defaultTag = Reflect.get(t, COMPONENT_DEFAULT_TAG);
690
717
  if (!isString(defaultTag)) return void 0;
691
718
  return getAsProp(child) ?? defaultTag;
692
719
  }
@@ -706,22 +733,34 @@ function isTag(...args) {
706
733
  return tag !== void 0 && set2.has(tag);
707
734
  }
708
735
 
709
- // ../../lib/adapter-utils/src/invariant.ts
710
- function panic(message) {
711
- throw new Error(message);
712
- }
713
- function invariant(condition, message) {
714
- if (!condition) panic(message);
715
- }
716
-
717
- // ../../lib/adapter-utils/src/runtime/apply-display-name.ts
718
- function applyDisplayName(component, name) {
719
- Object.assign(component, { displayName: name ?? "PolymorphicComponent" });
720
- }
721
-
722
- // ../../lib/adapter-utils/src/runtime/define-component.ts
723
- function defineContractComponent(options) {
724
- return (factory) => factory(options);
736
+ // ../../lib/adapter-utils/src/runtime/finalize-component.ts
737
+ function finalizeComponent(component, defaultTag, subComponents) {
738
+ if (typeof defaultTag === "string") {
739
+ Object.assign(component, { [COMPONENT_DEFAULT_TAG]: defaultTag });
740
+ }
741
+ return assembleCompoundComponent(component, subComponents);
742
+ }
743
+
744
+ // ../../lib/adapter-utils/src/runtime/is-factory-options-like.ts
745
+ var FACTORY_OPTIONS_FIELD_VALIDATORS = {
746
+ tag: (v) => v === void 0 || isString(v),
747
+ name: (v) => v === void 0 || isString(v),
748
+ defaults: (v) => v === void 0 || isObject(v),
749
+ normalize: (v) => v === void 0 || isFunction(v),
750
+ styling: (v) => v === void 0 || isObject(v),
751
+ enforcement: (v) => v === void 0 || isObject(v),
752
+ diagnostics: (v) => v === void 0 || isObject(v),
753
+ subComponents: (v) => v === void 0 || isObject(v),
754
+ onElement: (v) => v === void 0 || isFunction(v)
755
+ };
756
+ function isFactoryOptionsLike(options, extraFieldValidators) {
757
+ if (!isObject(options)) return false;
758
+ const validators = { ...FACTORY_OPTIONS_FIELD_VALIDATORS, ...extraFieldValidators };
759
+ for (const [key, value] of Object.entries(options)) {
760
+ const validate = validators[key];
761
+ if (!validate || !validate(value)) return false;
762
+ }
763
+ return true;
725
764
  }
726
765
 
727
766
  // ../../lib/contract/src/aria/aria-role-policy.ts
@@ -2874,7 +2913,7 @@ function getChildProp(child, key) {
2874
2913
  if (!isVNodeLike(child)) return void 0;
2875
2914
  const { props } = child;
2876
2915
  if (!isObject(props)) return void 0;
2877
- return props[key];
2916
+ return Reflect.get(props, key);
2878
2917
  }
2879
2918
 
2880
2919
  // ../core/src/html/contracts/aria/landmarks.ts
@@ -3571,6 +3610,13 @@ function buildRuntime(options) {
3571
3610
  return built;
3572
3611
  }
3573
3612
 
3613
+ // ../../adapters/vue/src/is-polymorphic-component.ts
3614
+ function isPolymorphicComponent(value) {
3615
+ if (!isObject(value)) return false;
3616
+ if (!("displayName" in value)) return true;
3617
+ return value.displayName === void 0 || isString(value.displayName);
3618
+ }
3619
+
3574
3620
  // ../../adapters/vue/src/render.ts
3575
3621
  import { cloneVNode, h as h3 } from "vue";
3576
3622
 
@@ -3669,7 +3715,7 @@ function prepareRenderState(runtime, attrs, filterProps) {
3669
3715
  className
3670
3716
  };
3671
3717
  }
3672
- function buildElementProps(props, className) {
3718
+ function buildElementProps(props, className, elementRef) {
3673
3719
  const { role, ...rest } = props;
3674
3720
  return {
3675
3721
  ...normalizeListenerKeys(rest),
@@ -3677,11 +3723,17 @@ function buildElementProps(props, className) {
3677
3723
  // normalizeClass turns it into '' and still emits class="", while the client patcher
3678
3724
  // removes the attribute outright. Omitting the key entirely keeps both paths consistent.
3679
3725
  ...className !== void 0 && { class: className },
3680
- ...isKnownAriaRole(role) && { role }
3726
+ ...isKnownAriaRole(role) && { role },
3727
+ // Vue calls a function-ref with the element on mount and with null on unmount, same
3728
+ // contract as React/Preact's callback refs — a natural fit for FactoryOptions.onElement.
3729
+ // Cast: Vue's own VNodeRef type also accepts a resolved *component* instance, since refs
3730
+ // can target components as well as elements — irrelevant here, this is only ever attached
3731
+ // to an intrinsic host tag, which always resolves to a real Element.
3732
+ ...elementRef !== void 0 && { ref: elementRef }
3681
3733
  };
3682
3734
  }
3683
- function renderIntrinsic(state, runtime, slots) {
3684
- const elementProps = buildElementProps(state.props, state.className);
3735
+ function renderIntrinsic(state, runtime, slots, elementRef) {
3736
+ const elementProps = buildElementProps(state.props, state.className, elementRef);
3685
3737
  const domProps = runtime.resolveAria(state.tag, elementProps).props;
3686
3738
  return h3(state.tag, domProps, slots.default ? { default: slots.default } : void 0);
3687
3739
  }
@@ -3694,12 +3746,13 @@ function validateSlotDirectives(directives, validator) {
3694
3746
  }
3695
3747
  return true;
3696
3748
  }
3697
- function tryRenderAsChild(state, children, discarded, validator) {
3749
+ function tryRenderAsChild(state, children, discarded, validator, elementRef) {
3698
3750
  if (!validateSlotDirectives(state.directives, validator)) return null;
3699
3751
  if (discarded > 0) validator.warnDiscardedChildren(discarded);
3700
3752
  const attrs = {
3701
3753
  ...pickAttributes(state.props),
3702
- ...state.className !== void 0 && { class: state.className }
3754
+ ...state.className !== void 0 && { class: state.className },
3755
+ ...elementRef !== void 0 && { ref: elementRef }
3703
3756
  };
3704
3757
  const slottable = extractSlottable(children);
3705
3758
  if (slottable) return slottable.rebuild(cloneVNode(slottable.child, attrs));
@@ -3714,20 +3767,31 @@ function render({
3714
3767
  state,
3715
3768
  slots,
3716
3769
  slotValidator,
3717
- childrenEvaluator
3770
+ childrenEvaluator,
3771
+ elementRef
3718
3772
  }) {
3719
3773
  const { vnodes: children, discarded } = normalizeChildren(slots);
3720
3774
  if (process.env.NODE_ENV !== "production") {
3721
3775
  childrenEvaluator?.evaluate(children, { tag: state.tag, props: state.normalizedProps });
3722
3776
  runtime.options.htmlChildrenEvaluatorFn?.(state.tag)?.evaluate(children, { tag: state.tag, props: state.normalizedProps });
3723
3777
  }
3724
- const slotResult = tryRenderAsChild(state, children, discarded, slotValidator);
3725
- return slotResult ?? renderIntrinsic(state, runtime, slots);
3778
+ const slotResult = tryRenderAsChild(state, children, discarded, slotValidator, elementRef);
3779
+ return slotResult ?? renderIntrinsic(state, runtime, slots, elementRef);
3780
+ }
3781
+
3782
+ // ../../adapters/vue/src/to-vue-factory-options.ts
3783
+ var VUE_FIELD_VALIDATORS = {
3784
+ filterProps: (v) => v === void 0 || isFunction(v)
3785
+ };
3786
+ function isVueFactoryOptions(options) {
3787
+ return isFactoryOptionsLike(options, VUE_FIELD_VALIDATORS);
3726
3788
  }
3727
3789
 
3728
3790
  // ../../adapters/vue/src/create-contract-component.ts
3729
3791
  function createContractComponent(options) {
3792
+ invariant(isVueFactoryOptions(options), "options is not a valid VueFactoryOptions object");
3730
3793
  const bundle = buildRuntime(options);
3794
+ const { onElement } = options;
3731
3795
  const Component = defineComponent2({
3732
3796
  // normalizeOptions always supplies `name`, so displayName is always defined here —
3733
3797
  // the fallback only satisfies the type, which allows it to be absent in general.
@@ -3737,15 +3801,33 @@ function createContractComponent(options) {
3737
3801
  const state = computed(
3738
3802
  () => prepareRenderState(bundle.runtime, attrs, bundle.filterProps)
3739
3803
  );
3740
- return () => render({ ...bundle, state: state.value, slots });
3804
+ let cleanup;
3805
+ let boundElement = null;
3806
+ const onElementRef = onElement ? (element) => {
3807
+ if (element === boundElement) return;
3808
+ if (boundElement) {
3809
+ cleanup?.();
3810
+ cleanup = void 0;
3811
+ }
3812
+ boundElement = element;
3813
+ if (element) {
3814
+ cleanup = onElement(element, () => attrs) ?? void 0;
3815
+ }
3816
+ } : void 0;
3817
+ return () => render({ ...bundle, state: state.value, slots, elementRef: onElementRef });
3741
3818
  }
3742
3819
  });
3743
3820
  applyDisplayName(Component, options.name);
3744
- const defaultTag = bundle.runtime.options.defaultTag;
3745
- if (typeof defaultTag === "string") {
3746
- Object.assign(Component, { [COMPONENT_DEFAULT_TAG]: defaultTag });
3747
- }
3748
- return Component;
3821
+ const assembled = finalizeComponent(
3822
+ Component,
3823
+ bundle.runtime.options.defaultTag,
3824
+ options.subComponents
3825
+ );
3826
+ invariant(
3827
+ isPolymorphicComponent(assembled),
3828
+ "Generated component failed to satisfy the PolymorphicComponent shape"
3829
+ );
3830
+ return assembled;
3749
3831
  }
3750
3832
  export {
3751
3833
  Slottable,