snapshot of https://github.com/ueberdosis/tiptap.git (main, 2026-09-15; blobs > 131072B omitted)
Some checks failed
Verify / Install node dependencies (push) Failing after 2s
Verify / Build packages (push) Has been skipped
Verify / Check linting & formatting (push) Has been skipped
Verify / Run unit tests (push) Has been skipped
Verify / Run e2e tests (shard 1/4) (push) Has been skipped
Verify / Run e2e tests (shard 2/4) (push) Has been skipped
Verify / Run e2e tests (shard 3/4) (push) Has been skipped
Verify / Run e2e tests (shard 4/4) (push) Has been skipped
Publish / Resolve publish config (push) Failing after 2s
Publish / Send notification to Slack (push) Has been skipped
Publish / Send failure notification to Slack (push) Failing after 8s
Verify / Merge e2e reports (push) Has been skipped
Publish / Build (push) Has been skipped
Publish / Bump Versions OR Release to Registry (push) Has been skipped

This commit is contained in:
upstream-mirror
2026-09-15 21:11:02 +05:00
commit 083a46e93b
2115 changed files with 245958 additions and 0 deletions

View File

@@ -0,0 +1,59 @@
import { describe, expect, it } from 'vite-plus/test'
import { defineComponent, h } from 'vue'
import { undeclaredWidgetProps } from './undeclaredWidgetProps.js'
const render = () => h('span')
describe('undeclaredWidgetProps', () => {
it('declares the context props a component does not declare', () => {
const component = defineComponent({ props: { label: String }, render })
expect(undeclaredWidgetProps(component, { label: 'a' })).toEqual({
editor: null,
getPos: null,
})
})
it('skips props the component already declares', () => {
const component = defineComponent({
props: { editor: Object, getPos: Function, label: String },
render,
})
expect(undeclaredWidgetProps(component, { label: 'a' })).toEqual({})
})
it('declares passed props the component is missing', () => {
const component = defineComponent({ props: { label: String }, render })
expect(undeclaredWidgetProps(component, { label: 'a', count: 1 })).toEqual({
editor: null,
getPos: null,
count: null,
})
})
it('reads array prop declarations', () => {
const component = defineComponent({ props: ['label', 'get-pos'], render })
expect(undeclaredWidgetProps(component, { label: 'a' })).toEqual({ editor: null })
})
it('reads props from extends and mixins', () => {
const component = defineComponent({
extends: defineComponent({ props: { editor: Object }, render }),
mixins: [defineComponent({ props: { label: String }, render })],
render,
})
expect(undeclaredWidgetProps(component, { label: 'a' })).toEqual({ getPos: null })
})
it('handles a component without props', () => {
expect(undeclaredWidgetProps(defineComponent({ render }), {})).toEqual({
editor: null,
getPos: null,
})
})
})

View File

@@ -0,0 +1,57 @@
import type { Component } from 'vue'
import { camelize } from 'vue'
interface OptionsComponent {
props?: string[] | Record<string, unknown>
extends?: Component
mixins?: Component[]
}
function collectPropNames(component: Component | undefined, names: Set<string>): void {
const options = component as OptionsComponent | undefined
if (!options) {
return
}
options.mixins?.forEach(mixin => collectPropNames(mixin, names))
collectPropNames(options.extends, names)
const { props } = options
if (Array.isArray(props)) {
props.forEach(name => names.add(camelize(name)))
} else if (props) {
Object.keys(props).forEach(name => names.add(camelize(name)))
}
}
/**
* Builds prop declarations for the widget props a component does not declare itself.
* Declaring them keeps Vue from rendering them as DOM attributes, while leaving
* the component's own `type`, `default` and `validator` options untouched.
*
* @param component The widget component, including its `extends` and `mixins` chain.
* @param props The props passed through `VueWidgetRenderer`.
* @returns A Vue props object for the undeclared keys only.
* @example
* undeclaredWidgetProps(MyWidget, { label: 'a' }) // => { editor: null, getPos: null }
*/
export function undeclaredWidgetProps(
component: Component,
props: Record<string, any>,
): Record<string, null> {
const declared = new Set<string>()
collectPropNames(component, declared)
const declarations: Record<string, null> = {}
for (const name of ['editor', 'getPos', ...Object.keys(props)]) {
if (!declared.has(camelize(name))) {
declarations[name] = null
}
}
return declarations
}

View File

@@ -0,0 +1,42 @@
import type { FunctionalComponent } from 'vue'
import { undeclaredWidgetProps } from './undeclaredWidgetProps.js'
type PropsOption = FunctionalComponent['props']
/** Keeps an array declaration an array, so Vue does not read numeric keys as prop names. */
function mergePropsOption(declared: PropsOption, undeclared: Record<string, null>): PropsOption {
if (Array.isArray(declared)) {
return [...declared, ...Object.keys(undeclared)]
}
return { ...(declared as object), ...undeclared } as PropsOption
}
/**
* Wraps a functional component so it can be used as a widget decoration.
*
* A functional component is its own render function, so the options wrapper
* used for object components would spread it away and leave Vue with nothing
* to render.
*
* @param component The functional component.
* @param props The props passed through `VueWidgetRenderer`.
* @returns A functional component declaring the widget props.
* @example
* wrapFunctionalWidget(props => h('span', props.label), { label: 'a' })
*/
export function wrapFunctionalWidget(
component: FunctionalComponent,
props: Record<string, any>,
): FunctionalComponent {
const wrapped: FunctionalComponent = (componentProps, context) =>
component(componentProps, context)
wrapped.props = mergePropsOption(component.props, undeclaredWidgetProps(component, props))
wrapped.emits = component.emits
wrapped.inheritAttrs = component.inheritAttrs
wrapped.displayName = component.displayName ?? component.name
return wrapped
}