feat(editor): eslint/tsconfig migration + type fixes

@robonen/editor: migrate to eslint flat config + composite tsconfig; fix
convergence test type annotations.
This commit is contained in:
2026-06-07 16:30:05 +07:00
parent 626fbc70d8
commit 09272dffeb
136 changed files with 7248 additions and 0 deletions
@@ -0,0 +1,30 @@
import type { AllowedComponentProps, Component, IntrinsicElementAttributes, SetupContext, VNodeProps } from 'vue';
import { h } from 'vue';
import { renderSlotChild } from './Slot';
type FunctionalComponentContext = Omit<SetupContext, 'expose'>;
export interface PrimitiveProps {
as?: keyof IntrinsicElementAttributes | Component;
}
/**
* Polymorphic element renderer: renders `as` (a tag or component), or the single
* slotted child when `as === 'template'`. Local copy of the primitives helper.
*/
export function Primitive(props: PrimitiveProps & VNodeProps & AllowedComponentProps & Record<string, unknown>, ctx: FunctionalComponentContext) {
const as = props.as;
return as === 'template'
? renderSlotChild(ctx.slots, ctx.attrs)
: h(as!, ctx.attrs, ctx.slots);
}
Primitive.inheritAttrs = false;
Primitive.props = {
as: {
type: [String, Object],
default: 'div' as const,
},
};
+50
View File
@@ -0,0 +1,50 @@
import type { SetupContext, Slots, VNode } from 'vue';
import { Comment, Fragment, cloneVNode, warn } from 'vue';
import { getRawChildren } from './getRawChildren';
type FunctionalComponentContext = Omit<SetupContext, 'expose'>;
/**
* Renders a single child from the provided default slot, applying attrs to it.
* Shared between `<Slot>` and `<Primitive as="template">`.
*
* @param slots - Component slots
* @param attrs - Attrs to apply to the slotted child
* @returns Cloned VNode with merged attrs or null
*/
export function renderSlotChild(slots: Slots, attrs: Record<string, unknown>): VNode | null {
if (!slots.default) return null;
const raw = slots.default();
if (raw.length === 1) {
const only = raw[0] as VNode;
const t = only.type;
if (t !== Fragment && t !== Comment)
return cloneVNode(only, attrs, true);
}
const children = getRawChildren(raw);
if (!children.length) return null;
if (__DEV__ && children.length > 1) {
warn('<Slot> can only be used on a single element or component.');
}
return cloneVNode(children[0]!, attrs, true);
}
/**
* A component that renders a single child from its default slot, applying the
* provided attributes to it.
*
* @param _ - Props (unused)
* @param context - Setup context containing slots and attrs
* @returns Cloned VNode with merged attrs or null
*/
export function Slot(_: Record<string, unknown>, { slots, attrs }: FunctionalComponentContext) {
return renderSlotChild(slots, attrs);
}
Slot.inheritAttrs = false;
@@ -0,0 +1,43 @@
import type { VNode } from 'vue';
import { Comment, Fragment } from 'vue';
import { PatchFlags } from '@vue/shared';
/**
* Recursively extracts and flattens VNodes from potentially nested Fragments
* while filtering out Comment nodes. Local copy of the primitives helper to keep
* `@robonen/editor` self-contained.
*
* @param children - Array of VNodes to process
* @returns Flattened array of non-Comment VNodes
*/
export function getRawChildren(children: VNode[]): VNode[] {
const result: VNode[] = [];
flatten(children, result);
return result;
}
function flatten(children: VNode[], result: VNode[]): void {
let keyedFragmentCount = 0;
const startIdx = result.length;
for (let i = 0, len = children.length; i < len; i++) {
const child = children[i]!;
if (child.type === Fragment) {
if (child.patchFlag & PatchFlags.KEYED_FRAGMENT) {
keyedFragmentCount++;
}
flatten(child.children as VNode[], result);
}
else if (child.type !== Comment) {
result.push(child);
}
}
if (keyedFragmentCount > 1) {
for (let i = startIdx; i < result.length; i++) {
result[i]!.patchFlag = PatchFlags.BAIL;
}
}
}
+4
View File
@@ -0,0 +1,4 @@
export { Primitive } from './Primitive';
export type { PrimitiveProps } from './Primitive';
export { Slot, renderSlotChild } from './Slot';
export { getRawChildren } from './getRawChildren';