wj-elements
Version:
WebJET Elements is a modern set of user interface tools harnessing the power of web components designed to simplify web application development.
382 lines (381 loc) • 16.6 kB
JavaScript
var __defProp = Object.defineProperty;
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
import WJElement from "./wje-element.js";
import { getBasePath } from "./base-path.js";
import InfiniteScroll from "./wje-infinite-scroll.js";
import Input from "./wje-input.js";
import Tooltip from "./wje-tooltip.js";
import { event } from "./event.js";
const styles = "/*\n[ Wj Icon Picker ]\n*/\n\n:host {\n /*padding: 0 1rem;*/\n}\n\n.anchor {\n width: var(--wje-icon-picker-icon-size);\n height: var(--wje-icon-picker-icon-size);\n padding: var(--wje-icon-picker-padding);\n border-width: var(--wje-icon-picker-border-width);\n border-style: var(--wje-icon-picker-border-style);\n border-color: var(--wje-icon-picker-border-color);\n box-sizing: border-box;\n border-radius: var(--wje-icon-picker-radius);\n}\n\n.picker {\n width: 320px;\n height: 271px;\n box-shadow: var(--wje-icon-picker-shadow);\n border-radius: var(--wje-icon-picker-radius);\n border-width: var(--wje-icon-picker-border-width);\n border-style: var(--wje-icon-picker-border-style);\n border-color: var(--wje-icon-picker-border-color);\n overflow: auto;\n padding: 1rem;\n background: var(--wje-icon-picker-background);\n}\n\n.icon-items {\n --icon-min-width: 2rem;\n display: grid;\n grid-gap: 0.5rem;\n grid-template-columns: repeat(auto-fit, minmax(var(--icon-min-width), 1fr));\n}\n\n.icon-item {\n box-sizing: border-box;\n display: flex;\n align-items: center;\n justify-content: center;\n text-align: center;\n color: inherit;\n padding: 0.25rem;\n min-height: var(--wje-icon-picker-icon-size);\n min-width: var(--wje-icon-picker-icon-size);\n text-decoration: none;\n\n &:hover {\n border-radius: 0.25rem;\n background: var(--wje-border-color);\n }\n}\n\n.wje-size {\n --wje-icon-size: 24px !important;\n}\n\nicon-item svg {\n width: var(--wje-icon-picker-icon-size);\n height: var(--wje-icon-picker-icon-size);\n}\n\nwje-input {\n --wje-input-border-radius: 4px;\n --wje-input-margin-bottom: 0;\n}\n\nwje-infinite-scroll {\n margin-top: 1rem;\n}\n\nwje-select {\n --wje-select-border-width: 0 0 1px 0 !important;\n --wje-select-border-radius: 0 !important;\n margin-bottom: 1rem;\n}\n\n.anchor wje-icon {\n --wje-icon-size: 100% !important;\n}\n";
class IconPicker extends WJElement {
/**
* Creates an instance of IconPicker.
* @class
*/
constructor() {
super();
/**
* Dependencies of the IconPicker component.
* @property {object} dependencies
*/
__publicField(this, "dependencies", {
"wje-input": Input,
"wje-infinite-scroll": InfiniteScroll,
"wje-tooltip": Tooltip
});
__publicField(this, "className", "IconPicker");
/**
* Handles the selection of an icon from a given input element and updates the relevant properties and events.
* @function selectIcon
* @param {HTMLElement} icon The icon element that was selected. It must have a 'name' attribute and may optionally have a 'filled' attribute.
* The function performs the following actions:
* - Retrieves the name of the selected icon from its 'name' attribute.
* - Determines the style type ('filled' or 'outline') based on the presence of the 'filled' attribute.
* - Searches for a matching object in the `transformedObjects` array based on name and style.
* - Creates a new `wje-icon` element and assigns its attributes based on the selected icon's properties.
* - Updates the selected object's properties and assigns it to the `value`, `icon`, and `type` properties of the class.
* - Dispatches a custom event (`wje-icon-picker:select`) to signal a change in the selected icon.
*/
__publicField(this, "selectIcon", (icon) => {
var _a, _b;
let name = icon.getAttribute("name");
let stylesType = icon.hasAttribute("filled") ? "filled" : "outline";
let uniqueObject = this.transformedObjects.find(
(i) => i.name === name && Object.keys(i.styles)[0] === stylesType
);
const iconElement = document.createElement("wje-icon");
iconElement.setAttribute("name", name);
if ((_a = uniqueObject.styles) == null ? void 0 : _a.hasOwnProperty("filled")) iconElement.setAttribute("filled", "");
uniqueObject.icon = iconElement;
this.value = uniqueObject;
this.icon = uniqueObject.name;
this.type = ((_b = uniqueObject.styles) == null ? void 0 : _b.hasOwnProperty("filled")) ? "filled" : "outline";
event.dispatchCustomEvent(this, "wje-icon-picker:select", uniqueObject);
});
/**
* Converts an object of tags into a transformed array of objects, separating `filled` and `outline` styles.
* The function processes an input object containing tags, extracts its values,
* and for each tag that has both `filled` and `outline` styles, splits them into
* two separate objects. Tags without `filled` styles remain unchanged.
* @param {object} tags The input object containing tags as properties. Each property is an object with a `styles` key.
* @param {object} tags[].styles The styles object containing `filled` and/or `outline` styles.
* @param {object} [tags[].styles.outline] The outline style object, if present.
* @param {object} [tags[].styles.filled] The filled style object, if present.
* @returns {Array<object>} An array of transformed objects. Objects with both `filled` and `outline` styles are split into separate objects, each containing only one style.
* @example
* const tags = {
* hourglass: {
* styles: {
* outline: { ... },
* filled: { ... },
* }
* },
* clock: {
* styles: {
* outline: { ... },
* }
* }
* };
* const result = convertObject(tags);
* console.log(result);
* // [
* // { styles: { outline: { ... } } },
* // { styles: { filled: { ... } } },
* // { styles: { outline: { ... } } }
* // ]
*/
__publicField(this, "convertObject", (tags = {}) => {
let originalObjects = Object.values(tags);
let transformedObjects = [];
for (let i = 0; i < originalObjects.length; i++) {
const obj = originalObjects[i];
if (obj.styles.filled) {
transformedObjects.push(
{ ...obj, styles: { outline: obj.styles.outline } },
{ ...obj, styles: { filled: obj.styles.filled } }
);
} else {
transformedObjects.push(obj);
}
}
return transformedObjects;
});
/**
* Converts an icon data object into an HTML element structure.
* This function creates a styled HTML element that represents an icon with a tooltip.
* The tooltip displays the name of the icon, and the icon itself is styled based on
* whether it uses the `filled` style.
* @param {object} data The icon data object.
* @returns {HTMLElement} A `div` element containing the icon wrapped in a `wje-tooltip`. The tooltip displays the icon name, and the `wje-icon` element represents the icon with attributes set according to the data.
* @example
* const iconData = {
* name: "hourglass",
* styles: {
* filled: { ... }
* }
* };
* const htmlElement = dataToHtml(iconData);
* document.body.appendChild(htmlElement);
*
* // The resulting structure:
* // <div class="icon-item">
* // <wje-tooltip content="hourglass">
* // <wje-icon name="hourglass" size="large" filled></wje-icon></wje-icon>
* // </wje-tooltip>
* // </div>
*/
__publicField(this, "dataToHtml", (data) => {
let iconItem = document.createElement("div");
iconItem.classList.add("icon-item");
let tooltip = document.createElement("wje-tooltip");
tooltip.setAttribute("content", data.name);
let icon = document.createElement("wje-icon");
icon.setAttribute("name", data.name);
icon.setAttribute("size", "large");
if (data.styles.hasOwnProperty("filled")) icon.setAttribute("filled", "");
tooltip.appendChild(icon);
iconItem.appendChild(tooltip);
return iconItem;
});
/**
* Searches icons based on user input.
* This method handles the input event and filters the available icons based on the provided search string.
* The filtering is performed on an index that combines icon names and their tags.
* The results are then adjusted for infinite scrolling.
* @param {Event} e The input event (e.g., `wje-input:input`) containing the search query details.
*/
__publicField(this, "searchIcon", (e) => {
var _a;
const query = !((_a = e.detail) == null ? void 0 : _a.value) ? "" : e.detail.value.toLowerCase();
const results = this.index.filter((item) => item.searchText.includes(query));
if (results.length === 0) {
this.clearIconsContainer();
const noResultsDiv = document.createElement("div");
noResultsDiv.setAttribute("part", "no-results");
noResultsDiv.classList.add("no-results");
noResultsDiv.textContent = "No icons found";
this.infiniteScroll.querySelector(".icon-items").append(noResultsDiv);
return;
}
this.infiniteScroll.unScrollEvent();
this.infiniteScroll.setCustomData = (page = 0) => {
const data = results.slice(page * this.size, page * this.size + this.size);
return {
data,
page,
size: this.size,
totalPages: Math.ceil(results.length / this.size)
};
};
this.infiniteScroll.reset();
this.infiniteScroll.scrollEvent();
this.infiniteScroll.loadPages(0);
});
}
/**
* Setter for the markerPosition property.
* @param {any} value The value to set.
*/
set size(value) {
this.setAttribute("size", value);
}
/**
* Getter for the markerPosition property.
* @returns {any} size The value of the markerPosition property.
*/
get size() {
return this.getAttribute("size") || 60;
}
/**
* Setter for the value property.
* @param value
*/
set icon(value) {
this.setAttribute("icon", value);
}
/**
* Getter for the value property.
* @returns {string}
*/
get icon() {
return this.getAttribute("icon");
}
/**
* Setter for the value property.
* @param value
*/
set type(value) {
this.setAttribute("type", value);
}
/**
* Getter for the value property.
* @returns {string}
*/
get type() {
return this.getAttribute("type");
}
/**
* Returns the CSS styles for the component.
* @static
* @returns {CSSStyleSheet}
*/
static get cssStyleSheet() {
return styles;
}
/**
* Returns the list of attributes to observe for changes.
* @static
* @returns {Array<string>}
*/
static get observedAttributes() {
return [];
}
/**
* Sets up the attributes for the component.
*/
setupAttributes() {
this.isShadowRoot = "open";
this.syncAria();
}
/**
* Prepares data before the draw operation by fetching tags, transforming objects, and creating an index.
* @returns {Promise<void>} Resolves when data preparation is complete.
*/
async beforeDraw() {
this.tags = Object.values(await this.getTags());
this.transformedObjects = this.convertObject(this.tags);
this.index = this.transformedObjects.map((item) => ({
...item,
searchText: `${item.name.toLowerCase()} ${item.tags.join(" ").toLowerCase()}`
}));
}
/**
* Draws and initializes the native color picker component on the DOM.
* This method creates and appends the necessary elements, including input and infinite scroll components,
* and sets their attributes and event listeners. It also provides custom data handling for infinite scrolling
* and manages the way icons are displayed based on input.
* @returns {DocumentFragment} A document fragment containing the fully constructed color picker component.
*/
draw() {
let fragment = document.createDocumentFragment();
let native = document.createElement("div");
native.classList.add("native-color-picker");
if (this.hasAttribute("icon") && this.icon) {
let icon = document.createElement("wje-icon");
icon.setAttribute("name", this.icon);
if (this.type === "filled")
icon.setAttribute("filled", "");
this.selectIcon(icon);
}
let picker = document.createElement("div");
picker.classList.add("picker");
let input = document.createElement("wje-input");
input.classList.add("input");
input.setAttribute("variant", "standard");
input.setAttribute("placeholder", "type to filter...");
input.setAttribute("clearable", "");
input.addEventListener("wje-input:input", this.debounce(this.searchIcon.bind(this), 300));
input.addEventListener("wje-input:clear", this.searchIcon);
let infiniteScroll = new InfiniteScroll();
infiniteScroll.setAttribute("url", getBasePath("assets/tags.json"));
infiniteScroll.setAttribute("placement", ".icon-items");
infiniteScroll.setAttribute("size", this.size);
infiniteScroll.setAttribute("height", "223px");
infiniteScroll.innerHTML = '<div class="icon-items"></div>';
picker.append(input, infiniteScroll);
native.append(picker);
fragment.append(native);
this.input = input;
this.picker = picker;
this.infiniteScroll = infiniteScroll;
this.infiniteScroll.dataToHtml = this.dataToHtml;
this.infiniteScroll.compareFunction = (i, item) => i.name === item.name;
this.infiniteScroll.setCustomData = (page = 0) => {
return {
data: this.transformedObjects.slice(page * this.size, page * this.size + this.size),
page,
size: this.size,
totalPages: Math.round(this.transformedObjects.length / this.size)
};
};
return fragment;
}
/**
* Executes actions that occur after the component finishes its draw phase. Sets up event listeners for input clear
* and infinite scroll item clicks, resets initialization state, and rebinds scroll-related events.
* @returns {void} Does not return a value.
*/
afterDraw() {
this.syncAria();
this.addEventListener("wje-input:clear", () => {
this.clearIconsContainer();
this.infiniteScroll.scrollEvent();
this.infiniteScroll.loadPages(0);
});
this.addEventListener("wje-infinite-scroll:click-item", (e) => {
e.preventDefault();
e.stopImmediatePropagation();
this.selectIcon(e.detail.context.querySelector("wje-icon"));
});
this.init = false;
}
/**
* Sync ARIA attributes on host.
*/
syncAria() {
if (!this.hasAttribute("role")) {
this.setAriaState({ role: "group" });
}
const ariaLabel = this.getAttribute("aria-label");
const label = this.getAttribute("label");
if (!ariaLabel && label) {
this.setAriaState({ label });
}
}
/**
* Initializes the component.
*/
initial() {
this.infiniteScroll.scrollEvent();
}
/**
* Gets the tags.
* @returns {Promise<Array>} The tags of the component.
*/
async getTags() {
const response = await fetch(getBasePath("assets/tags.json"));
return response.json();
}
/**
* Called when the component is disconnected.
*/
beforeDisconnect() {
this.init = false;
}
/**
* Clears the icons container.
*/
clearIconsContainer() {
this.context.querySelector(".icon-items").innerHTML = "";
}
/**
* Creates a debounced version of the provided function that delays its execution
* until after the specified delay has passed since the last time it was invoked.
* @param {Function} fn The function to debounce.
* @param {number} delay The delay duration in milliseconds.
* @returns {Function} A new debounced function that delays the execution of the original function.
*/
debounce(fn, delay) {
let timeout;
return (...args) => {
clearTimeout(timeout);
timeout = setTimeout(() => fn(...args), delay);
};
}
}
IconPicker.define("wje-icon-picker", IconPicker);
export {
IconPicker as default
};
//# sourceMappingURL=wje-icon-picker.js.map