@nectary/theme-base
Advanced tools
| module.exports = { | ||
| presets: [ | ||
| ['@babel/preset-env', { modules: false }], | ||
| '@babel/preset-typescript', | ||
| ], | ||
| ignore: ['*.d.ts', /node_modules/], | ||
| } |
| interface ThemeDefinition { | ||
| name: string; | ||
| path: string; | ||
| targetlibVersion?: string; | ||
| } | ||
| interface ThemeLoadOptions extends ThemeDefinition { | ||
| cdnUrl: string; | ||
| fallbackCdnUrl?: string; | ||
| } | ||
| interface GlobalThemeManagerInitOptions { | ||
| cdnUrl: string; | ||
| fallbackCdnUrl?: string; | ||
| targetlibVersion?: string; | ||
| themes: ThemeDefinition[]; | ||
| } | ||
| declare class GlobalThemeManagerImpl { | ||
| private static instance; | ||
| static getInstance(): GlobalThemeManagerImpl; | ||
| init(options: GlobalThemeManagerInitOptions): Promise<void>; | ||
| loadTheme(options: ThemeLoadOptions): Promise<string | null>; | ||
| whenLoaded(): Promise<void>; | ||
| private loadModule; | ||
| } | ||
| export declare const GlobalThemeManager: GlobalThemeManagerImpl; | ||
| export {}; |
| import { getThemeStore } from './global-theme-store'; | ||
| import { loadModuleWithFallback } from './shared/cdn-loader'; | ||
| class GlobalThemeManagerImpl { | ||
| static instance = null; | ||
| static getInstance() { | ||
| if (GlobalThemeManagerImpl.instance == null) { | ||
| GlobalThemeManagerImpl.instance = new GlobalThemeManagerImpl(); | ||
| } | ||
| return GlobalThemeManagerImpl.instance; | ||
| } | ||
| async init(options) { | ||
| const store = getThemeStore(); | ||
| if (store.hasInitialized) { | ||
| console.warn('GlobalThemeManager has already been initialized'); | ||
| return; | ||
| } | ||
| store.hasInitialized = true; | ||
| store.cdnUrl = options.cdnUrl; | ||
| store.fallbackCdnUrl = options.fallbackCdnUrl ?? ''; | ||
| const version = options.targetlibVersion ?? ''; | ||
| try { | ||
| await Promise.all(options.themes.map(async theme => { | ||
| const themeVersion = theme.targetlibVersion ?? version; | ||
| try { | ||
| const module = await this.loadModule(store.cdnUrl, store.fallbackCdnUrl, themeVersion, theme.path); | ||
| store.themes.set(theme.name, { | ||
| css: module.default, | ||
| name: theme.name, | ||
| targetlibVersion: themeVersion | ||
| }); | ||
| } catch (error) { | ||
| console.error(`Failed to load theme "${theme.name}":`, error); | ||
| } | ||
| })); | ||
| } finally { | ||
| store.loadPromise.resolve(); | ||
| } | ||
| } | ||
| async loadTheme(options) { | ||
| const store = getThemeStore(); | ||
| const existing = store.themes.get(options.name); | ||
| if (existing != null) { | ||
| return existing.css; | ||
| } | ||
| const version = options.targetlibVersion ?? ''; | ||
| const fallback = options.fallbackCdnUrl ?? ''; | ||
| try { | ||
| const module = await this.loadModule(options.cdnUrl, fallback, version, options.path); | ||
| const css = module.default; | ||
| store.themes.set(options.name, { | ||
| css, | ||
| name: options.name, | ||
| targetlibVersion: version | ||
| }); | ||
| return css; | ||
| } catch (error) { | ||
| console.error(`Failed to load theme "${options.name}":`, error); | ||
| return null; | ||
| } | ||
| } | ||
| whenLoaded() { | ||
| return getThemeStore().loadPromise.promise; | ||
| } | ||
| loadModule(cdnUrl, fallbackCdnUrl, version, modulePath) { | ||
| return loadModuleWithFallback({ | ||
| cdnUrl, | ||
| fallbackCdnUrl, | ||
| version, | ||
| modulePath, | ||
| logPrefix: 'Theme' | ||
| }); | ||
| } | ||
| } | ||
| export const GlobalThemeManager = GlobalThemeManagerImpl.getInstance(); |
| import type { DeferredPromise } from './shared/deferred-promise'; | ||
| interface ThemeEntry { | ||
| css: string; | ||
| name: string; | ||
| targetlibVersion: string; | ||
| } | ||
| interface GlobalThemeStore { | ||
| hasInitialized: boolean; | ||
| cdnUrl: string; | ||
| fallbackCdnUrl: string; | ||
| themes: Map<string, ThemeEntry>; | ||
| loadPromise: DeferredPromise; | ||
| } | ||
| export declare const getThemeStore: () => GlobalThemeStore; | ||
| export {}; |
| import { createDeferredPromise } from './shared/deferred-promise'; | ||
| const THEME_STORE_KEY = Symbol.for('NectaryThemeStore'); | ||
| export const getThemeStore = () => { | ||
| if (window[THEME_STORE_KEY] != null) { | ||
| return window[THEME_STORE_KEY]; | ||
| } | ||
| const store = { | ||
| hasInitialized: false, | ||
| cdnUrl: '', | ||
| fallbackCdnUrl: '', | ||
| themes: new Map(), | ||
| loadPromise: createDeferredPromise() | ||
| }; | ||
| Object.defineProperty(window, THEME_STORE_KEY, { | ||
| value: store, | ||
| enumerable: false, | ||
| writable: false, | ||
| configurable: false | ||
| }); | ||
| return store; | ||
| }; |
| export { GlobalThemeManager } from './global-theme-manager'; | ||
| export { getThemeStore } from './global-theme-store'; |
| export { GlobalThemeManager } from './global-theme-manager'; | ||
| export { getThemeStore } from './global-theme-store'; |
| interface LoadModuleOptions { | ||
| cdnUrl: string; | ||
| fallbackCdnUrl: string; | ||
| version: string; | ||
| modulePath: string; | ||
| logPrefix?: string; | ||
| } | ||
| export declare const loadModuleWithFallback: <TModule = Record<string, any>>(options: LoadModuleOptions) => Promise<TModule>; | ||
| export {}; |
| const getImportPath = (cdnUrl, version, modulePath) => { | ||
| if (cdnUrl.length === 0) { | ||
| return null; | ||
| } | ||
| const host = new URL(cdnUrl).host; | ||
| if (host === 'esm.sh') { | ||
| if (version.length !== 0) { | ||
| return `${cdnUrl}@${version}/es2022/${modulePath}.mjs`; | ||
| } | ||
| return `${cdnUrl}/${modulePath}`; | ||
| } | ||
| if (version.length !== 0) { | ||
| return `${cdnUrl}/${version}/${modulePath}.js`; | ||
| } | ||
| return `${cdnUrl}/latest/${modulePath}.js`; | ||
| }; | ||
| const FALLBACK_DELAY_MS = 2000; | ||
| export const loadModuleWithFallback = async options => { | ||
| const { | ||
| cdnUrl, | ||
| fallbackCdnUrl, | ||
| version, | ||
| modulePath, | ||
| logPrefix = 'CDN' | ||
| } = options; | ||
| const importPath = getImportPath(cdnUrl, version, modulePath); | ||
| const fallbackImportPath = getImportPath(fallbackCdnUrl, version, modulePath); | ||
| const promises = [import(/* webpackIgnore: true */importPath)]; | ||
| let timeoutId = null; | ||
| if (fallbackImportPath !== null) { | ||
| promises.push(new Promise(resolve => { | ||
| timeoutId = setTimeout(() => resolve(import(/* webpackIgnore: true */fallbackImportPath)), FALLBACK_DELAY_MS); | ||
| })); | ||
| } | ||
| try { | ||
| const module = await Promise.any(promises); | ||
| if (timeoutId !== null) { | ||
| clearTimeout(timeoutId); | ||
| } | ||
| return module; | ||
| } catch (error) { | ||
| if (error instanceof AggregateError) { | ||
| console.error(`${logPrefix} primary load failed: ${importPath}`, error.errors[0]); | ||
| if (fallbackImportPath !== null) { | ||
| console.error(`${logPrefix} fallback load failed: ${fallbackImportPath}`, error.errors[1]); | ||
| } | ||
| } else { | ||
| console.error(`${logPrefix} failed to load module: ${importPath}`, error); | ||
| } | ||
| throw error; | ||
| } | ||
| }; |
| export interface DeferredPromise<T = void> { | ||
| promise: Promise<T>; | ||
| resolve: (value: T) => void; | ||
| } | ||
| export declare const createDeferredPromise: <T = void>() => DeferredPromise<T>; |
| export const createDeferredPromise = () => { | ||
| let resolve; | ||
| const promise = new Promise(r => { | ||
| resolve = r; | ||
| }); | ||
| return { | ||
| promise, | ||
| resolve | ||
| }; | ||
| }; |
| { | ||
| "compilerOptions": { | ||
| "strict": true, | ||
| "target": "esnext", | ||
| "module": "esnext", | ||
| "moduleResolution": "Bundler", | ||
| "declaration": true, | ||
| "emitDeclarationOnly": true, | ||
| "outDir": "global-theme-manager/dist", | ||
| "lib": ["esnext", "dom"], | ||
| "skipLibCheck": true | ||
| }, | ||
| "include": ["global-theme-manager/**/*.ts"] | ||
| } |
+0
-12
@@ -131,14 +131,2 @@ .nectary-theme-base { | ||
| --sinch-comp-button-size-icon-xs: 16px; | ||
| --sinch-comp-button-spacing-gap-l: 12px; | ||
| --sinch-comp-button-spacing-gap-m: 12px; | ||
| --sinch-comp-button-spacing-gap-s: 12px; | ||
| --sinch-comp-button-spacing-gap-xs: 8px; | ||
| --sinch-comp-button-spacing-icon-only-padding-l: 8px; | ||
| --sinch-comp-button-spacing-icon-only-padding-m: 8px; | ||
| --sinch-comp-button-spacing-icon-only-padding-s: 4px; | ||
| --sinch-comp-button-spacing-icon-only-padding-xs: 4px; | ||
| --sinch-comp-button-spacing-padding-l: 16px; | ||
| --sinch-comp-button-spacing-padding-m: 16px; | ||
| --sinch-comp-button-spacing-padding-s: 16px; | ||
| --sinch-comp-button-spacing-padding-xs: 8px; | ||
| } |
+0
-12
@@ -23,14 +23,2 @@ .nectary-theme-base { | ||
| --sinch-comp-input-size-icon-s: 24px; | ||
| --sinch-comp-input-spacing-icon-gap-l: 8px; | ||
| --sinch-comp-input-spacing-icon-gap-m: 8px; | ||
| --sinch-comp-input-spacing-icon-gap-s: 8px; | ||
| --sinch-comp-input-spacing-padding-l: 12px; | ||
| --sinch-comp-input-spacing-padding-m: 12px; | ||
| --sinch-comp-input-spacing-padding-s: 12px; | ||
| --sinch-comp-input-spacing-slot-gap-l: 4px; | ||
| --sinch-comp-input-spacing-slot-gap-m: 4px; | ||
| --sinch-comp-input-spacing-slot-gap-s: 4px; | ||
| --sinch-comp-input-spacing-slot-padding-l: 4px; | ||
| --sinch-comp-input-spacing-slot-padding-m: 4px; | ||
| --sinch-comp-input-spacing-slot-padding-s: 4px; | ||
| } |
@@ -24,17 +24,2 @@ .nectary-theme-base { | ||
| --sinch-comp-select-button-size-icon-s: 24px; | ||
| --sinch-comp-select-button-spacing-gap-l: 8px; | ||
| --sinch-comp-select-button-spacing-gap-m: 8px; | ||
| --sinch-comp-select-button-spacing-gap-s: 8px; | ||
| --sinch-comp-select-button-spacing-left-slot-gap-l: 4px; | ||
| --sinch-comp-select-button-spacing-left-slot-gap-m: 4px; | ||
| --sinch-comp-select-button-spacing-left-slot-gap-s: 4px; | ||
| --sinch-comp-select-button-spacing-left-slot-offset-l: -4px; | ||
| --sinch-comp-select-button-spacing-left-slot-offset-m: -4px; | ||
| --sinch-comp-select-button-spacing-left-slot-offset-s: -4px; | ||
| --sinch-comp-select-button-spacing-padding-left-l: 12px; | ||
| --sinch-comp-select-button-spacing-padding-left-m: 12px; | ||
| --sinch-comp-select-button-spacing-padding-left-s: 12px; | ||
| --sinch-comp-select-button-spacing-padding-right-l: 12px; | ||
| --sinch-comp-select-button-spacing-padding-right-m: 8px; | ||
| --sinch-comp-select-button-spacing-padding-right-s: 4px; | ||
| } |
+18
-3
| { | ||
| "name": "@nectary/theme-base", | ||
| "version": "1.10.0", | ||
| "version": "1.11.0", | ||
| "main": "index.css", | ||
| "exports": { | ||
| ".": "./index.css", | ||
| "./global-theme-manager": "./global-theme-manager/dist/index.js", | ||
| "./*": "./*" | ||
| }, | ||
| "files": [ | ||
| "**/*.css", | ||
| "*.json", | ||
| "*.js" | ||
| "*.js", | ||
| "global-theme-manager/dist/**" | ||
| ], | ||
| "scripts": { | ||
| "build": "rm -rf global-theme-manager/dist global-theme-manager/shared && mkdir -p global-theme-manager/shared && cp ../../shared/cdn-loader.ts ../../shared/deferred-promise.ts global-theme-manager/shared/ && babel global-theme-manager --extensions '.ts' --out-dir global-theme-manager/dist && tsc --declaration --emitDeclarationOnly" | ||
| }, | ||
| "publishConfig": { | ||
| "access": "public" | ||
| }, | ||
| "main": "index.css" | ||
| "devDependencies": { | ||
| "@babel/cli": "^7.25.6", | ||
| "@babel/core": "^7.22.20", | ||
| "@babel/preset-env": "^7.22.20", | ||
| "@babel/preset-typescript": "^7.22.15" | ||
| } | ||
| } |
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
126280
4.77%70
20.69%2131
9.9%4
Infinity%2
100%