Skip to content
Draft
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
40 changes: 36 additions & 4 deletions packages/vue-instantsearch/src/util/vue-compat/index-vue2.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ export { Vue, Vue2, isVue2, isVue3, version };

const augmentCreateElement =
(createElement) =>
(tag, propsWithClassName = {}, ...children) => {
const { className, ...props } = propsWithClassName;
(tag, propsWithClassName, ...children) => {
const { className, ...props } = propsWithClassName || {};

if (typeof tag === 'function') {
return tag(
Expand All @@ -23,12 +23,33 @@ const augmentCreateElement =

if (typeof tag === 'string') {
const { on, style, attrs, domProps, nativeOn, key, ...rest } = props;
// React-style `onClick` / `onAuxClick` props (e.g. from shared
// `instantsearch-ui-components` JSX) need to be remapped to Vue 2's
// `on: { click, auxclick }` event API so they don't fall through to
// `attrs` and end up rendered as literal HTML attributes.
const reactStyleHandlers = {};
const remainingAttrs = {};
Object.keys(rest).forEach((prop) => {
if (
prop.length > 2 &&
prop[0] === 'o' &&
prop[1] === 'n' &&
prop[2] === prop[2].toUpperCase() &&
typeof rest[prop] === 'function'
) {
reactStyleHandlers[prop.slice(2).toLowerCase()] = rest[prop];
} else {
remainingAttrs[prop] = rest[prop];
}
});
return createElement(
tag,
{
class: className || props.class,
attrs: attrs || rest,
on,
attrs: attrs || remainingAttrs,
on: Object.keys(reactStyleHandlers).length
? Object.assign({}, reactStyleHandlers, on)
: on,
nativeOn,
style,
domProps,
Expand All @@ -51,6 +72,17 @@ export function renderCompat(fn) {
};
}

/**
* Fragment shim for the augmented JSX renderer used by `renderCompat`.
* Functional pragmas in `instantsearch-ui-components` use
* `<Fragment>{children}</Fragment>` to skip wrapping markup; Vue 2 has no
* native fragment, so we return the children array directly and let Vue 2
* flatten it into the surrounding vnode.
*/
export const Fragment = function Fragment(props) {
return props && props.children !== undefined ? props.children : null;
};

export function getDefaultSlot(component) {
return component.$slots.default;
}
Expand Down
16 changes: 15 additions & 1 deletion packages/vue-instantsearch/src/util/vue-compat/index-vue3.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,28 @@ const isVue2 = false;
const isVue3 = true;
const Vue2 = undefined;

export { createApp, createSSRApp, h, version, nextTick } from 'vue';
export { createApp, createSSRApp, h, version, nextTick, Fragment } from 'vue';
export { Vue, Vue2, isVue2, isVue3 };

export function renderCompat(fn) {
function h(tag, props, ...childrenArray) {
const children = childrenArray.length > 0 ? childrenArray : undefined;
// Components from `instantsearch-ui-components` are React-style functional
// components that read `children` from props. Vue 3 instead puts children
// in `slots.default`, which would leave `children` undefined and break
// components like `Button` that render `{children}`. Mirror the Vue 2
// augmented `h` and forward children as a prop for plain function tags
// (excluding Vue's own components/symbols like `Fragment`).
if (typeof tag === 'function') {
return Vue.h(
tag,
Object.assign({}, props || {}, { children }),
children
);
}
if (
typeof props === 'object' &&
props !== null &&
(props.attrs || props.props || props.scopedSlots || props.on)
) {
// In vue 3, we no longer wrap with `attrs` or `props` key.
Expand Down
Loading