Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(vue): custom element internal properties are no longer overridden in vue 3.1.0 #23738

Merged
merged 3 commits into from
Aug 9, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 7 additions & 7 deletions core/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
"@rollup/plugin-node-resolve": "^8.4.0",
"@rollup/plugin-virtual": "^2.0.3",
"@stencil/sass": "1.3.2",
"@stencil/vue-output-target": "^0.5.0",
"@stencil/vue-output-target": "^0.5.1",
"@types/jest": "^26.0.20",
"@types/node": "^14.6.0",
"@types/puppeteer": "5.4.3",
Expand Down
89 changes: 64 additions & 25 deletions packages/vue/src/vue-component-lib/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,19 @@ const UPDATE_VALUE_EVENT = 'update:modelValue';
const MODEL_VALUE = 'modelValue';
const ROUTER_LINK_VALUE = 'routerLink';
const NAV_MANAGER = 'navManager';
const ROUTER_PROP_REFIX = 'router';
const ROUTER_PROP_PREFIX = 'router';

/**
* Starting in Vue 3.1.0, all properties are
* added as keys to the props object, even if
* they are not being used. In order to correctly
* account for both value props and v-model props,
* we need to check if the key exists for Vue <3.1.0
* and then check if it is not undefined for Vue >= 3.1.0.
* See https://github.com/vuejs/vue-next/issues/3889
*/
const EMPTY_PROP = Symbol();
const DEFAULT_EMPTY_PROP = { default: EMPTY_PROP };

interface NavManager<T = any> {
navigate: (options: T) => void;
Expand Down Expand Up @@ -59,8 +71,8 @@ export const defineContainer = <Props>(
customElements.define(name, customElement);
}

const Container = defineComponent<Props & InputProps>((props, { attrs, slots, emit }) => {
let modelPropValue = (props as any)[modelProp];
const Container = defineComponent<Props & InputProps>((props: any, { attrs, slots, emit }) => {
let modelPropValue = props[modelProp];
const containerRef = ref<HTMLElement>();
const classes = new Set(getComponentClasses(attrs.class));
const onVnodeBeforeMount = (vnode: VNode) => {
Expand Down Expand Up @@ -92,30 +104,32 @@ export const defineContainer = <Props>(
const hasRouter = currentInstance?.appContext?.provides[NAV_MANAGER];
const navManager: NavManager | undefined = hasRouter ? inject(NAV_MANAGER) : undefined;
const handleRouterLink = (ev: Event) => {
const { routerLink } = props as any;
if (!routerLink) return;

const routerProps = Object.keys(props).filter(p => p.startsWith(ROUTER_PROP_REFIX));
const { routerLink } = props;
if (routerLink === EMPTY_PROP) return;

if (navManager !== undefined) {
let navigationPayload: any = { event: ev };
routerProps.forEach(prop => {
navigationPayload[prop] = (props as any)[prop];
});
for (const key in props) {
const value = props[key];
if (props.hasOwnProperty(key) && key.startsWith(ROUTER_PROP_PREFIX) && value !== EMPTY_PROP) {
navigationPayload[key] = value;
}
}

navManager.navigate(navigationPayload);
} else {
console.warn('Tried to navigate, but no router was found. Make sure you have mounted Vue Router.');
}
}

return () => {
modelPropValue = (props as any)[modelProp];
modelPropValue = props[modelProp];

getComponentClasses(attrs.class).forEach(value => {
classes.add(value);
});

const oldClick = (props as any).onClick;
const oldClick = props.onClick;
const handleClick = (ev: Event) => {
if (oldClick !== undefined) {
oldClick(ev);
Expand All @@ -125,26 +139,43 @@ export const defineContainer = <Props>(
}
}

let propsToAdd = {
...props,
let propsToAdd: any = {
ref: containerRef,
class: getElementClasses(containerRef, classes),
onClick: handleClick,
onVnodeBeforeMount: (modelUpdateEvent) ? onVnodeBeforeMount : undefined
};

/**
* We can use Object.entries here
* to avoid the hasOwnProperty check,
* but that would require 2 iterations
* where as this only requires 1.
*/
for (const key in props) {
const value = props[key];
if (props.hasOwnProperty(key) && value !== EMPTY_PROP) {
propsToAdd[key] = value;
}
}

if (modelProp) {
/**
* Starting in Vue 3.1.0, all properties are
* added as keys to the props object, even if
* they are not being used. In order to correctly
* account for both value props and v-model props,
* we need to check if the key exists for Vue <3.1.0
* and then check if it is not undefined for Vue >= 3.1.0.
* If form value property was set using v-model
* then we should use that value.
* Otherwise, check to see if form value property
* was set as a static value (i.e. no v-model).
*/
propsToAdd = {
...propsToAdd,
[modelProp]: props.hasOwnProperty(MODEL_VALUE) && props[MODEL_VALUE] !== undefined ? props.modelValue : modelPropValue
if (props[MODEL_VALUE] !== EMPTY_PROP) {
propsToAdd = {
...propsToAdd,
[modelProp]: props[MODEL_VALUE]
}
} else if (modelPropValue !== EMPTY_PROP) {
propsToAdd = {
...propsToAdd,
[modelProp]: modelPropValue
}
}
}

Expand All @@ -153,9 +184,17 @@ export const defineContainer = <Props>(
});

Container.displayName = name;
Container.props = [...componentProps, ROUTER_LINK_VALUE];

Container.props = {
[ROUTER_LINK_VALUE]: DEFAULT_EMPTY_PROP
};

componentProps.forEach(componentProp => {
Container.props[componentProp] = DEFAULT_EMPTY_PROP;
});

if (modelProp) {
Container.props.push(MODEL_VALUE);
Container.props[MODEL_VALUE] = DEFAULT_EMPTY_PROP;
Container.emits = [UPDATE_VALUE_EVENT, externalModelUpdateEvent];
}

Expand Down
Loading