UNPKG

@nextcloud/vue

Version:
407 lines (406 loc) 14.6 kB
import '../assets/NcEmojiPicker-B7xDz_8U.css'; import data from "emoji-mart-vue-fast/data/all.json"; import { Picker, Emoji, EmojiIndex } from "emoji-mart-vue-fast/src/index.js"; import { isFocusable } from "tabbable"; import { createElementBlock, openBlock, mergeProps, createElementVNode, createCommentVNode, toDisplayString, resolveComponent, createBlock, withCtx, createVNode, withKeys, withModifiers, createSlots, normalizeStyle, renderSlot, normalizeProps, guardReactiveProps } from "vue"; import { _ as _export_sfc } from "./_plugin-vue_export-helper-1tPrXgE0.mjs"; import { N as NcColorPicker } from "./NcColorPicker-Kc0JqRtp.mjs"; import { u as useTrapStackControl } from "./useTrapStackControl-B6cEicto.mjs"; import { s as setCurrentSkinTone, g as getCurrentSkinTone } from "./emoji-BY_D0V5K.mjs"; import { r as register, j as t42, k as t37, l as t16, m as t5, a as t } from "./_l10n-DrTiip5c.mjs"; import { C as Color } from "./colors-Go3zmZRD.mjs"; import { N as NcButton } from "./NcButton-Dc8V4Urj.mjs"; import { N as NcPopover } from "./NcPopover-C-MTaPCs.mjs"; import { _ as _sfc_main$2 } from "./NcTextField.vue_vue_type_script_setup_true_lang-D1y_LfGJ.mjs"; const _sfc_main$1 = { name: "CircleIcon", emits: ["click"], props: { title: { type: String }, fillColor: { type: String, default: "currentColor" }, size: { type: Number, default: 24 } } }; const _hoisted_1$1 = ["aria-hidden", "aria-label"]; const _hoisted_2$1 = ["fill", "width", "height"]; const _hoisted_3$1 = { d: "M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2Z" }; const _hoisted_4$1 = { key: 0 }; function _sfc_render$1(_ctx, _cache, $props, $setup, $data, $options) { return openBlock(), createElementBlock("span", mergeProps(_ctx.$attrs, { "aria-hidden": $props.title ? null : "true", "aria-label": $props.title, class: "material-design-icon circle-icon", role: "img", onClick: _cache[0] || (_cache[0] = ($event) => _ctx.$emit("click", $event)) }), [ (openBlock(), createElementBlock("svg", { fill: $props.fillColor, class: "material-design-icon__svg", width: $props.size, height: $props.size, viewBox: "0 0 24 24" }, [ createElementVNode("path", _hoisted_3$1, [ $props.title ? (openBlock(), createElementBlock("title", _hoisted_4$1, toDisplayString($props.title), 1)) : createCommentVNode("", true) ]) ], 8, _hoisted_2$1)) ], 16, _hoisted_1$1); } const IconCircle = /* @__PURE__ */ _export_sfc(_sfc_main$1, [["render", _sfc_render$1]]); register(t5, t16, t37, t42); let emojiIndex; const i18n = { search: t("Search emoji"), notfound: t("No emoji found"), categories: { search: t("Search results"), recent: t("Frequently used"), smileys: t("Smileys & Emotion"), people: t("People & Body"), nature: t("Animals & Nature"), foods: t("Food & Drink"), activity: t("Activities"), places: t("Travel & Places"), objects: t("Objects"), symbols: t("Symbols"), flags: t("Flags"), custom: t("Custom") } }; const skinTonePalette = [ new Color(255, 222, 52, t("Neutral skin color")), new Color(228, 205, 166, t("Light skin tone")), new Color(250, 221, 192, t("Medium light skin tone")), new Color(174, 129, 87, t("Medium skin tone")), new Color(158, 113, 88, t("Medium dark skin tone")), new Color(96, 79, 69, t("Dark skin tone")) ]; const _sfc_main = { name: "NcEmojiPicker", components: { IconCircle, NcButton, NcColorPicker, NcPopover, NcTextField: _sfc_main$2, Emoji, Picker }, props: { /** * The emoji-set */ activeSet: { type: String, default: "native" }, /** * Show preview section when hovering emoji */ showPreview: { type: Boolean, default: false }, /** * Allow unselecting the selected emoji */ allowUnselect: { type: Boolean, default: false }, /** * Selected emoji to allow unselecting */ selectedEmoji: { type: String, default: "" }, /** * The fallback emoji in the preview section */ previewFallbackEmoji: { type: String, default: "grinning" }, /** * The fallback text in the preview section */ previewFallbackName: { type: String, default: t("Pick an emoji") }, /** * Whether to close the emoji picker after picking one */ closeOnSelect: { type: Boolean, default: true }, /** * Selector for the popover container */ container: { type: [Boolean, String, Object, Element], default: "body" } }, emits: [ "select", "selectData", "unselect" ], setup() { if (!emojiIndex) { emojiIndex = new EmojiIndex(data); } return { // Non-reactive constants emojiIndex, skinTonePalette, i18n }; }, data() { const currentSkinTone = getCurrentSkinTone(); return { /** * The current active color from the skin tone palette */ currentColor: skinTonePalette[currentSkinTone - 1], /** * The current active skin tone * * @type {1|2|3|4|5|6} */ currentSkinTone, search: "", open: false }; }, computed: { native() { return this.activeSet === "native"; } }, created() { useTrapStackControl(() => this.open); }, methods: { t, clearSearch() { this.search = ""; this.$refs.search.focus(); }, /** * Update the current skin tone by the result of the color picker * * @param {string} color Color set */ onChangeSkinTone(color) { const index = this.skinTonePalette.findIndex((tone) => tone.color.toLowerCase() === color.toLowerCase()); if (index > -1) { this.currentSkinTone = index + 1; this.currentColor = this.skinTonePalette[index]; setCurrentSkinTone(this.currentSkinTone); } }, select(emojiObject) { this.$emit("select", emojiObject.native); this.$emit("selectData", emojiObject); if (this.closeOnSelect) { this.open = false; } }, unselect() { this.$emit("unselect"); }, afterShow() { this.$refs.search.focus(); }, afterHide() { if (!document.activeElement || this.$refs.picker.$el.contains(document.activeElement) || !isFocusable(document.activeElement)) { this.$refs.popover.$el.querySelector('button, [role="button"]')?.focus(); } }, /** * Manually handle Tab navigation skipping emoji buttons. * Navigation over emojis is handled by Arrow keys. * * @param {KeyboardEvent} event - Keyboard event */ handleTabNavigationSkippingEmojis(event) { const current = event.target; const focusable = Array.from(this.$refs.picker.$el.querySelectorAll("button:not(.emoji-mart-emoji), input")); if (!event.shiftKey) { const nextNode = focusable.find((node) => current.compareDocumentPosition(node) & Node.DOCUMENT_POSITION_FOLLOWING) || focusable[0]; nextNode.focus(); } else { const prevNode = focusable.findLast((node) => current.compareDocumentPosition(node) & Node.DOCUMENT_POSITION_PRECEDING) || focusable.at(-1); prevNode.focus(); } }, /** * Handle arrow navigation via <Picker>'s handlers with scroll bug fix * * @param {'onArrowLeft' | 'onArrowRight' | 'onArrowDown' | 'onArrowUp'} originalHandlerName - Picker's arrow keydown handler name * @param {KeyboardEvent} event - Keyboard event */ async callPickerArrowHandlerWithScrollFix(originalHandlerName, event) { this.$refs.picker[originalHandlerName](event); await this.$nextTick(); const selectedEmoji = this.$refs.picker.$el.querySelector(".emoji-mart-emoji-selected"); selectedEmoji?.scrollIntoView({ block: "center", inline: "center" }); } } }; const _hoisted_1 = { class: "nc-emoji-picker-container" }; const _hoisted_2 = { class: "search__wrapper" }; const _hoisted_3 = { class: "emoji-mart-category-label" }; const _hoisted_4 = { class: "emoji-mart-category-label" }; function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) { const _component_NcTextField = resolveComponent("NcTextField"); const _component_IconCircle = resolveComponent("IconCircle"); const _component_NcButton = resolveComponent("NcButton"); const _component_NcColorPicker = resolveComponent("NcColorPicker"); const _component_Emoji = resolveComponent("Emoji"); const _component_Picker = resolveComponent("Picker"); const _component_NcPopover = resolveComponent("NcPopover"); return openBlock(), createBlock(_component_NcPopover, { ref: "popover", shown: $data.open, "onUpdate:shown": _cache[6] || (_cache[6] = ($event) => $data.open = $event), container: $props.container, "popup-role": "dialog", "no-focus-trap": true, onAfterShow: $options.afterShow, onAfterHide: $options.afterHide }, { trigger: withCtx((slotProps) => [ renderSlot(_ctx.$slots, "default", normalizeProps(guardReactiveProps(slotProps)), void 0, true) ]), default: withCtx(() => [ createElementVNode("div", _hoisted_1, [ createVNode(_component_Picker, mergeProps({ ref: "picker", color: "var(--color-primary-element)", data: $setup.emojiIndex, emoji: $props.previewFallbackEmoji, i18n: $setup.i18n, native: $options.native, "emoji-size": 20, "per-line": 8, "picker-styles": { width: "320px" }, "show-preview": $props.showPreview, skin: $data.currentSkinTone, "show-skin-tones": false, title: $props.previewFallbackName, role: "dialog", "aria-modal": "true", "aria-label": $options.t("Emoji picker") }, _ctx.$attrs, { onKeydown: withKeys(withModifiers($options.handleTabNavigationSkippingEmojis, ["prevent"]), ["tab"]), onSelect: $options.select }), createSlots({ searchTemplate: withCtx(({ onSearch }) => [ createElementVNode("div", _hoisted_2, [ createVNode(_component_NcTextField, { ref: "search", modelValue: $data.search, "onUpdate:modelValue": [ _cache[0] || (_cache[0] = ($event) => $data.search = $event), ($event) => onSearch($data.search) ], class: "search", label: $options.t("Search"), "label-visible": true, placeholder: $setup.i18n.search, "trailing-button-icon": "close", "trailing-button-label": $options.t("Clear search"), "show-trailing-button": $data.search !== "", onKeydown: [ _cache[1] || (_cache[1] = withKeys(($event) => $options.callPickerArrowHandlerWithScrollFix("onArrowLeft", $event), ["left"])), _cache[2] || (_cache[2] = withKeys(($event) => $options.callPickerArrowHandlerWithScrollFix("onArrowRight", $event), ["right"])), _cache[3] || (_cache[3] = withKeys(($event) => $options.callPickerArrowHandlerWithScrollFix("onArrowDown", $event), ["down"])), _cache[4] || (_cache[4] = withKeys(($event) => $options.callPickerArrowHandlerWithScrollFix("onArrowUp", $event), ["up"])), _cache[5] || (_cache[5] = withKeys(($event) => _ctx.$refs.picker.onEnter($event), ["enter"])) ], onTrailingButtonClick: ($event) => { $options.clearSearch(); onSearch(""); } }, null, 8, ["modelValue", "label", "placeholder", "trailing-button-label", "show-trailing-button", "onTrailingButtonClick", "onUpdate:modelValue"]), createVNode(_component_NcColorPicker, { "palette-only": "", container: $props.container, palette: $setup.skinTonePalette, "model-value": $data.currentColor.color, "onUpdate:modelValue": $options.onChangeSkinTone }, { default: withCtx(() => [ createVNode(_component_NcButton, { "aria-label": $options.t("Skin tone"), variant: "tertiary-no-background" }, { icon: withCtx(() => [ createVNode(_component_IconCircle, { style: normalizeStyle({ color: $data.currentColor.color }), title: $data.currentColor.name, size: 20 }, null, 8, ["style", "title"]) ]), _: 1 }, 8, ["aria-label"]) ]), _: 1 }, 8, ["container", "palette", "model-value", "onUpdate:modelValue"]) ]) ]), _: 2 }, [ $props.allowUnselect && $props.selectedEmoji ? { name: "customCategory", fn: withCtx(() => [ createElementVNode("div", _hoisted_3, [ createElementVNode("h3", _hoisted_4, toDisplayString($options.t("Selected")), 1) ]), createVNode(_component_Emoji, { class: "emoji-selected", data: $setup.emojiIndex, emoji: $props.selectedEmoji, native: "", size: 32, onClick: $options.unselect }, null, 8, ["data", "emoji", "onClick"]), createVNode(_component_Emoji, { class: "emoji-delete", data: $setup.emojiIndex, emoji: ":x:", native: "", size: 10, onClick: $options.unselect }, null, 8, ["data", "onClick"]) ]), key: "0" } : void 0 ]), 1040, ["data", "emoji", "i18n", "native", "show-preview", "skin", "title", "aria-label", "onKeydown", "onSelect"]) ]) ]), _: 3 }, 8, ["shown", "container", "onAfterShow", "onAfterHide"]); } const NcEmojiPicker = /* @__PURE__ */ _export_sfc(_sfc_main, [["render", _sfc_render], ["__scopeId", "data-v-f2f99131"]]); export { NcEmojiPicker as N }; //# sourceMappingURL=NcEmojiPicker-pM4Pg2yq.mjs.map