UNPKG

@trimble-oss/moduswebcomponents

Version:

Modus Web Components is a modern, accessible UI library built with Stencil JS that provides reusable web components following Trimble's Modus design system. This updated version focuses on improved flexibility, enhanced theming options, comprehensive cust

242 lines (235 loc) 7.96 kB
import { d as getRenderingRef, f as forceUpdate } from './p-BMvVSi6Y.js'; const appendToMap = (map, propName, value) => { const items = map.get(propName); if (!items) { map.set(propName, [value]); } else if (!items.includes(value)) { items.push(value); } }; const debounce = (fn, ms) => { let timeoutId; return (...args) => { if (timeoutId) { clearTimeout(timeoutId); } timeoutId = setTimeout(() => { timeoutId = 0; fn(...args); }, ms); }; }; /** * Check if a possible element isConnected. * The property might not be there, so we check for it. * * We want it to return true if isConnected is not a property, * otherwise we would remove these elements and would not update. * * Better leak in Edge than to be useless. */ const isConnected = (maybeElement) => !('isConnected' in maybeElement) || maybeElement.isConnected; const cleanupElements = debounce((map) => { for (let key of map.keys()) { map.set(key, map.get(key).filter(isConnected)); } }, 2_000); const stencilSubscription = () => { if (typeof getRenderingRef !== 'function') { // If we are not in a stencil project, we do nothing. // This function is not really exported by @stencil/core. return {}; } const elmsToUpdate = new Map(); return { dispose: () => elmsToUpdate.clear(), get: (propName) => { const elm = getRenderingRef(); if (elm) { appendToMap(elmsToUpdate, propName, elm); } }, set: (propName) => { const elements = elmsToUpdate.get(propName); if (elements) { elmsToUpdate.set(propName, elements.filter(forceUpdate)); } cleanupElements(elmsToUpdate); }, reset: () => { elmsToUpdate.forEach((elms) => elms.forEach(forceUpdate)); cleanupElements(elmsToUpdate); }, }; }; const unwrap = (val) => (typeof val === 'function' ? val() : val); const createObservableMap = (defaultState, shouldUpdate = (a, b) => a !== b) => { const unwrappedState = unwrap(defaultState); let states = new Map(Object.entries(unwrappedState ?? {})); const handlers = { dispose: [], get: [], set: [], reset: [], }; const reset = () => { // When resetting the state, the default state may be a function - unwrap it to invoke it. // otherwise, the state won't be properly reset states = new Map(Object.entries(unwrap(defaultState) ?? {})); handlers.reset.forEach((cb) => cb()); }; const dispose = () => { // Call first dispose as resetting the state would // cause less updates ;) handlers.dispose.forEach((cb) => cb()); reset(); }; const get = (propName) => { handlers.get.forEach((cb) => cb(propName)); return states.get(propName); }; const set = (propName, value) => { const oldValue = states.get(propName); if (shouldUpdate(value, oldValue, propName)) { states.set(propName, value); handlers.set.forEach((cb) => cb(propName, value, oldValue)); } }; const state = (typeof Proxy === 'undefined' ? {} : new Proxy(unwrappedState, { get(_, propName) { return get(propName); }, ownKeys(_) { return Array.from(states.keys()); }, getOwnPropertyDescriptor() { return { enumerable: true, configurable: true, }; }, has(_, propName) { return states.has(propName); }, set(_, propName, value) { set(propName, value); return true; }, })); const on = (eventName, callback) => { handlers[eventName].push(callback); return () => { removeFromArray(handlers[eventName], callback); }; }; const onChange = (propName, cb) => { const unSet = on('set', (key, newValue) => { if (key === propName) { cb(newValue); } }); // We need to unwrap the defaultState because it might be a function. // Otherwise we might not be sending the right reset value. const unReset = on('reset', () => cb(unwrap(defaultState)[propName])); return () => { unSet(); unReset(); }; }; const use = (...subscriptions) => { const unsubs = subscriptions.reduce((unsubs, subscription) => { if (subscription.set) { unsubs.push(on('set', subscription.set)); } if (subscription.get) { unsubs.push(on('get', subscription.get)); } if (subscription.reset) { unsubs.push(on('reset', subscription.reset)); } if (subscription.dispose) { unsubs.push(on('dispose', subscription.dispose)); } return unsubs; }, []); return () => unsubs.forEach((unsub) => unsub()); }; const forceUpdate = (key) => { const oldValue = states.get(key); handlers.set.forEach((cb) => cb(key, oldValue, oldValue)); }; return { state, get, set, on, onChange, use, dispose, reset, forceUpdate, }; }; const removeFromArray = (array, item) => { const index = array.indexOf(item); if (index >= 0) { array[index] = array[array.length - 1]; array.length--; } }; const createStore = (defaultState, shouldUpdate) => { const map = createObservableMap(defaultState, shouldUpdate); map.use(stencilSubscription()); return map; }; // eslint-disable-next-line @typescript-eslint/unbound-method const { state, onChange, reset, dispose } = createStore({ mode: 'light', theme: 'modus-modern', }); const themeStore = { state, onChange, setTheme: (theme) => (state.theme = theme), setMode: (mode) => (state.mode = mode), reset, dispose, }; const getStoredMode = () => { try { const stored = localStorage.getItem('modus-theme-config'); return stored ? JSON.parse(stored).mode : null; } catch (error) { console.warn('Failed to parse theme config from localStorage:', error); return null; } }; const getSystemPreferredMode = () => { const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches; return prefersDark ? 'dark' : 'light'; }; // Initialize theme store with system preferences and stored values const initializeThemeStore = (initialConfig) => { var _a, _b, _c; // Set theme with default fallback state.theme = (_a = initialConfig === null || initialConfig === void 0 ? void 0 : initialConfig.theme) !== null && _a !== void 0 ? _a : 'modus-modern'; // Mode resolution with priority: explicit config > stored preference > system preference state.mode = (_c = (_b = initialConfig === null || initialConfig === void 0 ? void 0 : initialConfig.mode) !== null && _b !== void 0 ? _b : getStoredMode()) !== null && _c !== void 0 ? _c : getSystemPreferredMode(); }; // Watch for system theme changes const watchSystemTheme = () => { const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)'); const handleChange = (e) => { if (!localStorage.getItem('preferred-mode')) { state.mode = e.matches ? 'dark' : 'light'; } }; mediaQuery.addEventListener('change', handleChange); return () => mediaQuery.removeEventListener('change', handleChange); }; export { initializeThemeStore as i, themeStore as t, watchSystemTheme as w };