@gitlab/ui
Version:
GitLab UI Components
181 lines (171 loc) • 6.17 kB
JavaScript
import { isVue3 } from '../vue';
function _arrayLikeToArray(r, a) {
(null == a || a > r.length) && (a = r.length);
for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e];
return n;
}
function _arrayWithHoles(r) {
if (Array.isArray(r)) return r;
}
function _iterableToArrayLimit(r, l) {
var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"];
if (null != t) {
var e,
n,
i,
u,
a = [],
f = !0,
o = !1;
try {
if (i = (t = t.call(r)).next, 0 === l) {
if (Object(t) !== t) return;
f = !1;
} else for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0);
} catch (r) {
o = !0, n = r;
} finally {
try {
if (!f && null != t.return && (u = t.return(), Object(u) !== u)) return;
} finally {
if (o) throw n;
}
}
return a;
}
}
function _nonIterableRest() {
throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
}
function _slicedToArray(r, e) {
return _arrayWithHoles(r) || _iterableToArrayLimit(r, e) || _unsupportedIterableToArray(r, e) || _nonIterableRest();
}
function _unsupportedIterableToArray(r, a) {
if (r) {
if ("string" == typeof r) return _arrayLikeToArray(r, a);
var t = {}.toString.call(r).slice(8, -1);
return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0;
}
}
// 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 _ref = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {},
_ref$once = _ref.once,
once = _ref$once === void 0 ? false : _ref$once;
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(_ref2 => {
let _ref3 = _slicedToArray(_ref2, 1),
key = _ref3[0];
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 _extractEventHandlers = extractEventHandlers(config),
handlers = _extractEventHandlers.handlers,
cleanConfig = _extractEventHandlers.cleanConfig;
const instance = new Component({
...cleanConfig,
parent,
bvParent: parent,
bvEventRoot
});
// Subscribe to events using $on/$once
handlers.forEach(_ref4 => {
let eventName = _ref4.eventName,
handler = _ref4.handler,
once = _ref4.once;
instance[once ? '$once' : '$on'](eventName, handler);
});
return instance;
};
export { createNewChildComponent, eventProp };