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
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:
59
packages/vue/src/utils/undeclaredWidgetProps.test.ts
Normal file
59
packages/vue/src/utils/undeclaredWidgetProps.test.ts
Normal 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,
|
||||
})
|
||||
})
|
||||
})
|
||||
57
packages/vue/src/utils/undeclaredWidgetProps.ts
Normal file
57
packages/vue/src/utils/undeclaredWidgetProps.ts
Normal 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
|
||||
}
|
||||
42
packages/vue/src/utils/wrapFunctionalWidget.ts
Normal file
42
packages/vue/src/utils/wrapFunctionalWidget.ts
Normal 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
|
||||
}
|
||||
Reference in New Issue
Block a user