@gitlab/ui
Version:
GitLab UI Components
223 lines (208 loc) • 7.32 kB
JavaScript
import { isString, has } from 'lodash-es';
import __vue_normalize__ from 'vue-runtime-helpers/dist/normalize-component.js';
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;
}
}
const PREFIX = '%{';
const SUFFIX = '}';
const START_SUFFIX = 'Start';
const END_SUFFIX = 'End';
const PLACE_HOLDER_REGEX = new RegExp(`(${PREFIX}[a-z]+[\\w-]*[a-z0-9]+${SUFFIX})`, 'gi');
function groupPlaceholdersByStartTag() {
let placeholders = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
return Object.entries(placeholders).reduce((acc, _ref) => {
let _ref2 = _slicedToArray(_ref, 2),
slotName = _ref2[0],
_ref2$ = _slicedToArray(_ref2[1], 2),
startTag = _ref2$[0],
endTag = _ref2$[1];
acc[startTag] = {
slotName,
endTag
};
return acc;
}, {});
}
function getPlaceholderDefinition(chunk, placeholdersByStartTag) {
const tagName = chunk.slice(PREFIX.length, -SUFFIX.length);
if (has(placeholdersByStartTag, tagName)) {
// Use provided custom placeholder definition
return {
...placeholdersByStartTag[tagName],
tagName
};
}
if (tagName.endsWith(START_SUFFIX)) {
// Tag conforms to default start/end tag naming convention
const slotName = tagName.slice(0, -START_SUFFIX.length);
return {
slotName,
endTag: `${slotName}${END_SUFFIX}`,
tagName
};
}
return {
slotName: tagName,
endTag: undefined,
tagName
};
}
var script = {
name: 'GlSprintf',
functional: true,
props: {
/**
* A translated string with named placeholders, e.g., "Written by %{author}".
*/
message: {
type: String,
required: true
},
/**
* An object mapping slot names to custom start/end placeholders. Use this
* to avoid changing an existing message, and in turn invalidating existing
* translations, in the case it uses non-default placeholders.
*/
placeholders: {
type: Object,
required: false,
default: undefined,
validator: value => Object.values(value).every(
// eslint-disable-next-line unicorn/no-array-callback-reference
tagPair => Array.isArray(tagPair) && tagPair.length === 2 && tagPair.every(isString))
}
},
/**
* Available slots are determined by the placeholders in the provided
* message prop. For example, a message of "Written by %{author}" has
* a slot called "author", and its content is used to replace "%{author}"
* in the rendered output. When two placeholders indicate a start and an
* end region in the message, e.g., "%{linkStart}foo%{linkEnd}", the common
* base name can be used as a scoped slot, where the content between the
* placeholders is passed via the `content` scoped slot prop.
* @slot * (arbitrary)
* @binding {string} content The content to place between start and end placeholders.
*/
render(createElement, context) {
// While a functional style is generally preferred, an imperative style is
// used here, as it lends itself better to the message parsing algorithm.
// This approach is also more performant, as it minimizes (relatively) object
// creation/garbage collection, which is important given how frequently this
// code may run on a given page.
let i = 0;
const vnodes = [];
const slots = context.scopedSlots;
const chunks = context.props.message.split(PLACE_HOLDER_REGEX);
const placeholdersByStartTag = groupPlaceholdersByStartTag(context.props.placeholders);
while (i < chunks.length) {
const chunk = chunks[i];
// Skip past this chunk now we have it
i += 1;
if (!PLACE_HOLDER_REGEX.test(chunk)) {
// Not a placeholder, so pass through as-is
vnodes.push(chunk);
continue;
}
const _getPlaceholderDefini = getPlaceholderDefinition(chunk, placeholdersByStartTag),
slotName = _getPlaceholderDefini.slotName,
endTag = _getPlaceholderDefini.endTag,
tagName = _getPlaceholderDefini.tagName;
if (endTag) {
// Peek ahead to find end placeholder, if any
const indexOfEnd = chunks.indexOf(`${PREFIX}${endTag}${SUFFIX}`, i);
if (indexOfEnd > -1) {
// We have a valid start/end placeholder pair! Extract the content
// between them and skip past the end placeholder
const content = chunks.slice(i, indexOfEnd);
i = indexOfEnd + 1;
if (!has(slots, slotName)) {
// Slot hasn't been provided; return placeholders and content as-is
vnodes.push(chunk, ...content, chunks[indexOfEnd]);
continue;
}
// Provide content to provided scoped slot
vnodes.push(slots[slotName]({
content: content.join('')
}));
continue;
}
}
// By process of elimination, chunk must be a plain placeholder
vnodes.push(has(slots, tagName) ? slots[tagName]() : chunk);
continue;
}
return vnodes;
}
};
/* script */
const __vue_script__ = script;
/* template */
/* style */
const __vue_inject_styles__ = undefined;
/* scoped */
const __vue_scope_id__ = undefined;
/* module identifier */
const __vue_module_identifier__ = undefined;
/* functional template */
const __vue_is_functional_template__ = undefined;
/* style inject */
/* style inject SSR */
/* style inject shadow dom */
const __vue_component__ = /*#__PURE__*/__vue_normalize__(
{},
__vue_inject_styles__,
__vue_script__,
__vue_scope_id__,
__vue_is_functional_template__,
__vue_module_identifier__,
false,
undefined,
undefined,
undefined
);
export { __vue_component__ as default };