@gitlab/ui
Version:
GitLab UI Components
134 lines (125 loc) • 4.32 kB
JavaScript
import { isVue3 } from '../vue';
// Regex to detect event handler props: onSomething or onSomethingOnce
const EVENT_HANDLER_RE = /^on([A-Z][a-zA-Z]*)$/;
const ONCE_SUFFIX = 'Once';
/**
* Converts an event name to a handler prop name.
* Use as computed property key with event constants: [eventProp(EVENT_NAME_HIDDEN)]
* @param {string} eventName - The event name (e.g., 'hidden', 'show', 'mouseenter')
* @param {Object} options - Options object
* @param {boolean} options.once - If true, returns the "once" variant (e.g., 'onHiddenOnce')
* @returns {string} The handler prop name (e.g., 'onHidden', 'onHiddenOnce')
*/
const eventProp = function (eventName) {
let {
once = false
} = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
const capitalized = eventName.charAt(0).toUpperCase() + eventName.slice(1);
return `on${capitalized}${once ? ONCE_SUFFIX : ''}`;
};
/**
* Parses a handler prop name to extract event name and once flag.
* Examples:
* onShow -> { eventName: 'show', once: false }
* onShowOnce -> { eventName: 'show', once: true }
* onMouseenter -> { eventName: 'mouseenter', once: false }
*/
const parseEventHandlerProp = propName => {
const match = propName.match(EVENT_HANDLER_RE);
if (!match) return null;
let eventPart = match[1];
const once = eventPart.endsWith(ONCE_SUFFIX);
if (once) eventPart = eventPart.slice(0, -ONCE_SUFFIX.length);
const eventName = eventPart.charAt(0).toLowerCase() + eventPart.slice(1);
return {
eventName,
once
};
};
/**
* Extracts event handlers from config and separates them from other props.
* @param {Object} config - The config object with potential event handler props
* @returns {{ handlers: Array<{eventName: string, handler: Function, once: boolean}>, cleanConfig: Object }}
*/
const extractEventHandlers = config => {
const handlers = [];
const cleanConfig = {
...config
};
if (cleanConfig.propsData) {
const cleanPropsData = {};
Object.keys(cleanConfig.propsData).forEach(propName => {
const parsed = parseEventHandlerProp(propName);
if (parsed && typeof cleanConfig.propsData[propName] === 'function') {
handlers.push({
...parsed,
handler: cleanConfig.propsData[propName]
});
} else {
cleanPropsData[propName] = cleanConfig.propsData[propName];
}
});
cleanConfig.propsData = cleanPropsData;
}
return {
handlers,
cleanConfig
};
};
const getVue3Constructor = parent => {
return parent.$.appContext.config.globalProperties.constructor;
};
const BUILTIN_DIRECTIVES = ['show', 'model', 'bind', 'cloak', 'else', 'else-if', 'for', 'html', 'if', 'memo', 'on', 'once', 'pre', 'slot', 'text'];
const resolveComponentOptions = Component => {
const opts = typeof Component === 'function' && Component.options ? {
...Component.options
} : {
...Component
};
if (opts.directives) {
opts.directives = Object.fromEntries(Object.entries(opts.directives).filter(_ref => {
let [key] = _ref;
return !BUILTIN_DIRECTIVES.includes(key);
}));
}
return opts;
};
const createNewChildComponent = function (parent, Component) {
let config = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
const bvEventRoot = parent.$root ? parent.$root.$options.bvEventRoot || parent.$root : null;
// Vue 3: resolve component options and instantiate via the Vue constructor
// to ensure the component runs through the Vue 3 runtime path
if (isVue3(parent)) {
const Vue = getVue3Constructor(parent);
const componentOptions = resolveComponentOptions(Component);
return new Vue({
...componentOptions,
...config,
parent,
bvParent: parent,
bvEventRoot
});
}
// Vue 2: extract handlers and subscribe manually after creation
const {
handlers,
cleanConfig
} = extractEventHandlers(config);
const instance = new Component({
...cleanConfig,
parent,
bvParent: parent,
bvEventRoot
});
// Subscribe to events using $on/$once
handlers.forEach(_ref2 => {
let {
eventName,
handler,
once
} = _ref2;
instance[once ? '$once' : '$on'](eventName, handler);
});
return instance;
};
export { createNewChildComponent, eventProp };