@variiant-ui/react-vite 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +31 -0
- package/dist/index.d.ts +31 -0
- package/dist/index.js +626 -0
- package/dist/index.js.map +1 -0
- package/dist/runtime-api-CpNtY-8z.d.ts +61 -0
- package/dist/runtime.d.ts +13 -0
- package/dist/runtime.js +488 -0
- package/dist/runtime.js.map +1 -0
- package/package.json +53 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/plugin.ts","../src/runtime-core.ts","../src/runtime-singleton.ts","../src/runtime-api.ts"],"sourcesContent":["import fs from \"node:fs\";\nimport path from \"node:path\";\n\nimport type { Plugin } from \"vite\";\n\nexport type VariantManifest = {\n source: string;\n exportName?: string;\n selected?: string;\n displayName?: string;\n variants: string[] | Record<string, string>;\n};\n\nexport type VariantTargetEntry = {\n exportName: string;\n selected: string;\n displayName: string;\n variantImportPaths: Record<string, string>;\n};\n\nexport type VariantRegistryEntry = {\n key: string;\n sourceAbsolutePath: string;\n sourceRelativePath: string;\n sourceImportPath: string;\n hasDefaultExport: boolean;\n targets: Record<string, VariantTargetEntry>;\n};\n\nexport type VariantPluginOptions = {\n projectRoot?: string;\n variantsDir?: string;\n};\n\nconst sourceExtensions = [\".tsx\", \".ts\", \".jsx\", \".js\", \".mts\", \".mjs\", \".cts\", \".cjs\"];\n\nexport function variantPlugin(options: VariantPluginOptions = {}): Plugin {\n let projectRoot = \"\";\n let registry = new Map<string, VariantRegistryEntry>();\n\n const refreshRegistry = (): void => {\n projectRoot = options.projectRoot ? path.resolve(options.projectRoot) : process.cwd();\n registry = loadRegistry(projectRoot, options.variantsDir ?? \".variants\");\n };\n\n return {\n name: \"variiant-react-vite\",\n enforce: \"pre\",\n configResolved(config) {\n projectRoot = options.projectRoot ? path.resolve(options.projectRoot) : config.root;\n registry = loadRegistry(projectRoot, options.variantsDir ?? \".variants\");\n },\n configureServer(server) {\n server.watcher.add(path.join(projectRoot, options.variantsDir ?? \".variants\"));\n const reload = (): void => {\n refreshRegistry();\n server.ws.send({ type: \"full-reload\" });\n };\n\n server.watcher.on(\"add\", reload);\n server.watcher.on(\"change\", reload);\n server.watcher.on(\"unlink\", reload);\n },\n async resolveId(source, importer, resolveOptions) {\n if (!importer || source.startsWith(\"\\0\")) {\n return null;\n }\n\n const normalizedImporter = normalizePath(importer);\n if (\n normalizedImporter.startsWith(\"\\0variant-proxy:\") ||\n normalizedImporter.includes(\"/.variants/\")\n ) {\n return null;\n }\n\n const resolved = await this.resolve(source, importer, {\n ...resolveOptions,\n skipSelf: true,\n });\n\n if (!resolved) {\n return null;\n }\n\n const entry = registry.get(normalizePath(resolved.id));\n if (!entry) {\n return null;\n }\n\n return `\\0variant-proxy:${encodeURIComponent(entry.sourceAbsolutePath)}`;\n },\n load(id) {\n if (!id.startsWith(\"\\0variant-proxy:\")) {\n return null;\n }\n\n const sourceAbsolutePath = decodeURIComponent(id.slice(\"\\0variant-proxy:\".length));\n const entry = registry.get(normalizePath(sourceAbsolutePath));\n if (!entry) {\n throw new Error(`Missing variant registry entry for ${sourceAbsolutePath}`);\n }\n\n if (this.meta.watchMode) {\n return buildDevelopmentProxyModule(entry);\n }\n\n return buildProductionProxyModule(entry);\n },\n };\n}\n\nfunction loadRegistry(projectRoot: string, variantsDirName: string): Map<string, VariantRegistryEntry> {\n const variantsRoot = path.join(projectRoot, variantsDirName);\n const results = new Map<string, VariantRegistryEntry>();\n\n if (!fs.existsSync(variantsRoot)) {\n return results;\n }\n\n loadConventionalVariants(projectRoot, variantsRoot, results);\n loadManifestVariants(projectRoot, variantsRoot, results);\n\n return results;\n}\n\nfunction loadConventionalVariants(\n projectRoot: string,\n variantsRoot: string,\n results: Map<string, VariantRegistryEntry>,\n): void {\n const variantFiles = walkForVariantFiles(variantsRoot);\n\n for (const variantFilePath of variantFiles) {\n const relativePath = normalizePath(path.relative(variantsRoot, variantFilePath));\n const match = parseConventionalVariantPath(relativePath);\n if (!match) {\n continue;\n }\n\n const sourceAbsolutePath = normalizePath(path.resolve(projectRoot, match.sourceRelativePath));\n const entry = ensureRegistryEntry(results, projectRoot, sourceAbsolutePath, match.sourceRelativePath);\n const target = ensureTargetEntry(entry, {\n exportName: match.exportName,\n selected: \"source\",\n displayName: deriveDisplayName(match.sourceRelativePath, match.exportName),\n });\n\n if (match.variantName in target.variantImportPaths) {\n throw new Error(\n `Duplicate conventional variant \"${match.variantName}\" for ${match.sourceRelativePath}#${match.exportName}.`,\n );\n }\n\n target.variantImportPaths[match.variantName] = toImportPath(variantFilePath);\n }\n}\n\nfunction loadManifestVariants(\n projectRoot: string,\n variantsRoot: string,\n results: Map<string, VariantRegistryEntry>,\n): void {\n const configPaths = walkForVariantConfigs(variantsRoot);\n\n for (const configPath of configPaths) {\n const configDir = path.dirname(configPath);\n const parsed = JSON.parse(fs.readFileSync(configPath, \"utf8\")) as VariantManifest;\n\n if (!parsed.source || typeof parsed.source !== \"string\") {\n throw new Error(`Variant config ${configPath} must declare a string \"source\".`);\n }\n\n if (!parsed.variants || (!Array.isArray(parsed.variants) && typeof parsed.variants !== \"object\")) {\n throw new Error(`Variant config ${configPath} must declare \"variants\".`);\n }\n\n const sourceRelativePath = normalizePath(parsed.source);\n const sourceAbsolutePath = normalizePath(path.resolve(projectRoot, sourceRelativePath));\n const exportName = parsed.exportName ?? \"default\";\n const variantImportPaths = resolveVariantImportPaths(configDir, parsed.variants);\n const selected = parsed.selected ?? \"source\";\n\n if (selected !== \"source\" && !(selected in variantImportPaths)) {\n throw new Error(\n `Variant config ${configPath} selects \"${selected}\" but no matching variant file was found.`,\n );\n }\n\n const entry = ensureRegistryEntry(results, projectRoot, sourceAbsolutePath, sourceRelativePath);\n const target = ensureTargetEntry(entry, {\n exportName,\n selected,\n displayName: parsed.displayName ?? deriveDisplayName(sourceRelativePath, exportName),\n });\n\n if (Object.keys(target.variantImportPaths).length > 0) {\n throw new Error(\n `Duplicate variant target for ${sourceRelativePath}#${exportName}. Remove either the manifest or the conventional variant folder.`,\n );\n }\n\n target.selected = selected;\n target.displayName = parsed.displayName ?? target.displayName;\n target.variantImportPaths = variantImportPaths;\n }\n}\n\nfunction walkForVariantConfigs(rootDir: string): string[] {\n const results: string[] = [];\n\n const walk = (currentDir: string): void => {\n for (const entry of fs.readdirSync(currentDir, { withFileTypes: true })) {\n const entryPath = path.join(currentDir, entry.name);\n if (entry.isDirectory()) {\n walk(entryPath);\n continue;\n }\n\n if (entry.isFile() && entry.name === \"variant.json\") {\n results.push(entryPath);\n }\n }\n };\n\n walk(rootDir);\n return results.sort();\n}\n\nfunction walkForVariantFiles(rootDir: string): string[] {\n const results: string[] = [];\n\n const walk = (currentDir: string): void => {\n for (const entry of fs.readdirSync(currentDir, { withFileTypes: true })) {\n const entryPath = path.join(currentDir, entry.name);\n if (entry.isDirectory()) {\n walk(entryPath);\n continue;\n }\n\n if (\n entry.isFile() &&\n entry.name !== \"variant.json\" &&\n sourceExtensions.some((extension) => entry.name.endsWith(extension))\n ) {\n results.push(normalizePath(entryPath));\n }\n }\n };\n\n walk(rootDir);\n return results.sort();\n}\n\nfunction parseConventionalVariantPath(relativePath: string): {\n sourceRelativePath: string;\n exportName: string;\n variantName: string;\n} | null {\n const segments = relativePath.split(\"/\").filter(Boolean);\n if (segments.length < 3) {\n return null;\n }\n\n const sourceSegmentIndex = findSourceSegmentIndex(segments);\n if (sourceSegmentIndex === -1 || sourceSegmentIndex !== segments.length - 3) {\n return null;\n }\n\n const exportName = segments[sourceSegmentIndex + 1];\n const variantFileName = segments[sourceSegmentIndex + 2];\n const extension = sourceExtensions.find((candidate) => variantFileName.endsWith(candidate));\n if (!extension) {\n return null;\n }\n\n const variantName = variantFileName.slice(0, -extension.length);\n if (!exportName || !variantName) {\n return null;\n }\n\n return {\n sourceRelativePath: segments.slice(0, sourceSegmentIndex + 1).join(\"/\"),\n exportName,\n variantName,\n };\n}\n\nfunction findSourceSegmentIndex(segments: string[]): number {\n for (let index = segments.length - 3; index >= 0; index -= 1) {\n if (sourceExtensions.some((extension) => segments[index]?.endsWith(extension))) {\n return index;\n }\n }\n\n return -1;\n}\n\nfunction resolveVariantImportPaths(\n configDir: string,\n variants: VariantManifest[\"variants\"],\n): Record<string, string> {\n if (Array.isArray(variants)) {\n return Object.fromEntries(\n variants.map((variantName) => [\n variantName,\n toImportPath(resolveFile(configDir, variantName)),\n ]),\n );\n }\n\n return Object.fromEntries(\n Object.entries(variants).map(([variantName, relativePath]) => [\n variantName,\n toImportPath(resolveCustomPath(configDir, relativePath)),\n ]),\n );\n}\n\nfunction resolveFile(configDir: string, variantName: string): string {\n for (const extension of sourceExtensions) {\n const candidate = path.join(configDir, `${variantName}${extension}`);\n if (fs.existsSync(candidate)) {\n return normalizePath(candidate);\n }\n }\n\n throw new Error(\n `Could not find variant file for \"${variantName}\" in ${configDir}. Expected one of ${sourceExtensions.join(\", \")}.`,\n );\n}\n\nfunction resolveCustomPath(configDir: string, relativePath: string): string {\n const absolute = normalizePath(path.resolve(configDir, relativePath));\n if (fs.existsSync(absolute)) {\n return absolute;\n }\n\n throw new Error(`Variant file ${relativePath} does not exist in ${configDir}.`);\n}\n\nfunction ensureRegistryEntry(\n results: Map<string, VariantRegistryEntry>,\n projectRoot: string,\n sourceAbsolutePath: string,\n sourceRelativePath: string,\n): VariantRegistryEntry {\n const existing = results.get(sourceAbsolutePath);\n if (existing) {\n return existing;\n }\n\n const created: VariantRegistryEntry = {\n key: sourceRelativePath,\n sourceAbsolutePath,\n sourceRelativePath,\n sourceImportPath: toImportPath(sourceAbsolutePath),\n hasDefaultExport: detectHasDefaultExport(sourceAbsolutePath),\n targets: {},\n };\n\n created.key = normalizePath(path.relative(projectRoot, sourceAbsolutePath));\n results.set(sourceAbsolutePath, created);\n return created;\n}\n\nfunction ensureTargetEntry(\n entry: VariantRegistryEntry,\n target: Pick<VariantTargetEntry, \"exportName\" | \"selected\" | \"displayName\">,\n): VariantTargetEntry {\n const existing = entry.targets[target.exportName];\n if (existing) {\n return existing;\n }\n\n const created: VariantTargetEntry = {\n exportName: target.exportName,\n selected: target.selected,\n displayName: target.displayName,\n variantImportPaths: {},\n };\n\n entry.targets[target.exportName] = created;\n return created;\n}\n\nfunction buildDevelopmentProxyModule(entry: VariantRegistryEntry): string {\n const importLines = [\n `export * from ${JSON.stringify(entry.sourceImportPath)};`,\n `import * as SourceModule from ${JSON.stringify(entry.sourceImportPath)};`,\n `import { createVariantProxy, installVariantOverlay } from \"@variiant-ui/react-vite/runtime\";`,\n ];\n const outputLines: string[] = [\"installVariantOverlay();\"];\n\n for (const target of sortTargets(entry.targets)) {\n if (target.exportName === \"default\" && !entry.hasDefaultExport) {\n throw new Error(\n `Variant target ${entry.sourceRelativePath}#default requires the source module to have a default export.`,\n );\n }\n\n const targetIdentifier = toIdentifier(target.exportName === \"default\" ? \"default\" : target.exportName);\n const variantsIdentifier = `${targetIdentifier}Variants`;\n const proxyIdentifier = `${targetIdentifier}VariantProxy`;\n const variantLines = [` source: ${getSourceAccessExpression(target.exportName)},`];\n\n for (const [variantName, importPath] of Object.entries(target.variantImportPaths)) {\n const identifier = toIdentifier(`${target.exportName}-${variantName}`);\n importLines.push(`import ${identifier} from ${JSON.stringify(importPath)};`);\n variantLines.push(` ${JSON.stringify(variantName)}: ${identifier},`);\n }\n\n outputLines.push(`const ${variantsIdentifier} = {`);\n outputLines.push(variantLines.join(\"\\n\"));\n outputLines.push(\"};\");\n outputLines.push(\"\");\n outputLines.push(`const ${proxyIdentifier} = createVariantProxy({`);\n outputLines.push(` sourceId: ${JSON.stringify(buildSourceId(entry.sourceRelativePath, target.exportName))},`);\n outputLines.push(` displayName: ${JSON.stringify(target.displayName)},`);\n outputLines.push(` selected: ${JSON.stringify(target.selected)},`);\n outputLines.push(` variants: ${variantsIdentifier},`);\n outputLines.push(\"});\");\n outputLines.push(\"\");\n outputLines.push(buildVariantExport(target.exportName, proxyIdentifier));\n outputLines.push(\"\");\n }\n\n if (entry.hasDefaultExport && !(\"default\" in entry.targets)) {\n outputLines.push(\"export default SourceModule.default;\");\n }\n\n return `${importLines.join(\"\\n\")}\\n\\n${outputLines.join(\"\\n\")}\\n`;\n}\n\nfunction buildProductionProxyModule(entry: VariantRegistryEntry): string {\n const lines = [`export * from ${JSON.stringify(entry.sourceImportPath)};`];\n\n const defaultTarget = entry.targets.default;\n if (defaultTarget?.selected && defaultTarget.selected !== \"source\") {\n const selectedImport = defaultTarget.variantImportPaths[defaultTarget.selected];\n lines.push(`export { default } from ${JSON.stringify(selectedImport)};`);\n } else if (entry.hasDefaultExport) {\n lines.push(`export { default } from ${JSON.stringify(entry.sourceImportPath)};`);\n }\n\n for (const target of sortTargets(entry.targets)) {\n if (target.exportName === \"default\" || target.selected === \"source\") {\n continue;\n }\n\n const selectedImport = target.variantImportPaths[target.selected];\n lines.push(`export { default as ${target.exportName} } from ${JSON.stringify(selectedImport)};`);\n }\n\n return `${lines.join(\"\\n\")}\\n`;\n}\n\nfunction sortTargets(targets: Record<string, VariantTargetEntry>): VariantTargetEntry[] {\n return Object.values(targets).sort((left, right) => {\n if (left.exportName === \"default\") {\n return -1;\n }\n\n if (right.exportName === \"default\") {\n return 1;\n }\n\n return left.exportName.localeCompare(right.exportName);\n });\n}\n\nfunction getSourceAccessExpression(exportName: string): string {\n return exportName === \"default\"\n ? \"SourceModule.default\"\n : `SourceModule[${JSON.stringify(exportName)}]`;\n}\n\nfunction deriveDisplayName(sourceRelativePath: string, exportName: string): string {\n if (exportName !== \"default\") {\n return exportName;\n }\n\n const extension = sourceExtensions.find((candidate) => sourceRelativePath.endsWith(candidate)) ?? \"\";\n const fileName = path.posix.basename(sourceRelativePath, extension);\n const displayName = fileName === \"index\"\n ? path.posix.basename(path.posix.dirname(sourceRelativePath))\n : fileName;\n\n return humanize(displayName);\n}\n\nfunction humanize(value: string): string {\n return value\n .replace(/([a-z0-9])([A-Z])/g, \"$1 $2\")\n .replace(/[-_]+/g, \" \")\n .replace(/\\b\\w/g, (match) => match.toUpperCase());\n}\n\nfunction detectHasDefaultExport(sourceAbsolutePath: string): boolean {\n if (!fs.existsSync(sourceAbsolutePath)) {\n return false;\n }\n\n const source = fs.readFileSync(sourceAbsolutePath, \"utf8\");\n return /\\bexport\\s+default\\b/.test(source) || /\\bas\\s+default\\b/.test(source);\n}\n\nfunction toIdentifier(name: string): string {\n const safeName = name\n .replace(/[^a-zA-Z0-9]+(.)/g, (_match, nextCharacter: string) => nextCharacter.toUpperCase())\n .replace(/[^a-zA-Z0-9]/g, \"\");\n return `${safeName.charAt(0).toUpperCase() + safeName.slice(1)}Variant`;\n}\n\nfunction normalizePath(value: string): string {\n return value.split(path.sep).join(path.posix.sep);\n}\n\nfunction toImportPath(value: string): string {\n return normalizePath(value);\n}\n\nfunction buildSourceId(sourceRelativePath: string, exportName: string): string {\n return exportName === \"default\"\n ? sourceRelativePath\n : `${sourceRelativePath}#${exportName}`;\n}\n\nfunction buildVariantExport(exportName: string, proxyIdentifier: string): string {\n return exportName === \"default\"\n ? `export default ${proxyIdentifier};`\n : `export { ${proxyIdentifier} as ${exportName} };`;\n}\n","type Direction = 1 | -1;\n\nexport type Shortcut = string | string[];\n\nexport type VariantShortcutConfig = {\n toggleOverlay: Shortcut;\n nextComponent: Shortcut;\n previousComponent: Shortcut;\n nextVariant: Shortcut;\n previousVariant: Shortcut;\n closeOverlay: Shortcut;\n};\n\nexport const defaultShortcuts: VariantShortcutConfig = {\n toggleOverlay: [\"meta+shift+.\", \"ctrl+shift+.\"],\n nextComponent: [\"meta+alt+arrowdown\", \"ctrl+alt+arrowdown\"],\n previousComponent: [\"meta+alt+arrowup\", \"ctrl+alt+arrowup\"],\n nextVariant: [\"meta+shift+arrowright\", \"ctrl+shift+arrowright\"],\n previousVariant: [\"meta+shift+arrowleft\", \"ctrl+shift+arrowleft\"],\n closeOverlay: \"escape\",\n};\n\nexport type VariantDefinition = {\n sourceId: string;\n displayName: string;\n selected: string;\n variantNames: string[];\n};\n\nexport type RuntimeComponentRecord = VariantDefinition & {\n mountedCount: number;\n};\n\nexport type RuntimeState = {\n overlayOpen: boolean;\n activeSourceId: string | null;\n components: RuntimeComponentRecord[];\n};\n\nexport type VariantRuntimeSnapshot = RuntimeState & {\n selections: Record<string, string>;\n shortcutConfig: VariantShortcutConfig;\n};\n\nexport type VariantRuntimeStorage = {\n readSelections: () => Record<string, string>;\n writeSelections: (selections: Record<string, string>) => void;\n readShortcutOverrides: () => Partial<VariantShortcutConfig>;\n writeShortcutOverrides: (shortcutConfig: VariantShortcutConfig) => void;\n};\n\nexport type VariantRuntimeController = {\n define: (definition: VariantDefinition) => void;\n mount: (sourceId: string) => () => void;\n getSelectedVariant: (sourceId: string, fallback: string) => string;\n getSnapshot: () => VariantRuntimeSnapshot;\n subscribe: (listener: () => void) => () => void;\n actions: {\n toggleOverlay: () => void;\n closeOverlay: () => void;\n nextComponent: () => void;\n previousComponent: () => void;\n nextVariant: () => void;\n previousVariant: () => void;\n selectComponent: (sourceId: string | null) => void;\n selectVariant: (sourceId: string, variantName: string) => void;\n configureShortcuts: (overrides?: Partial<VariantShortcutConfig>) => void;\n };\n};\n\ntype ControllerState = RuntimeState & {\n selections: Record<string, string>;\n shortcutConfig: VariantShortcutConfig;\n};\n\nfunction rotate<T>(items: T[], currentIndex: number, direction: Direction): T | null {\n if (items.length === 0) {\n return null;\n }\n\n const nextIndex = (currentIndex + direction + items.length) % items.length;\n return items[nextIndex] ?? null;\n}\n\nfunction getMountedComponents(state: ControllerState): RuntimeComponentRecord[] {\n return state.components.filter((component) => component.mountedCount > 0);\n}\n\nfunction getActiveMountedComponent(state: ControllerState): RuntimeComponentRecord | null {\n const mounted = getMountedComponents(state);\n return mounted.find((component) => component.sourceId === state.activeSourceId) ?? mounted[0] ?? null;\n}\n\nexport function createVariantRuntimeController(options: {\n storage?: VariantRuntimeStorage;\n} = {}): VariantRuntimeController {\n const storage = options.storage;\n const listeners = new Set<() => void>();\n const definitions = new Map<string, VariantDefinition>();\n const mountedCounts = new Map<string, number>();\n const state: ControllerState = {\n overlayOpen: false,\n activeSourceId: null,\n components: [],\n selections: storage?.readSelections() ?? {},\n shortcutConfig: {\n ...defaultShortcuts,\n ...(storage?.readShortcutOverrides() ?? {}),\n },\n };\n\n const emit = (): void => {\n state.components = Array.from(definitions.values())\n .map((definition) => ({\n ...definition,\n selected: state.selections[definition.sourceId] ?? definition.selected,\n mountedCount: mountedCounts.get(definition.sourceId) ?? 0,\n }))\n .sort((left, right) => left.displayName.localeCompare(right.displayName));\n\n const mounted = getMountedComponents(state);\n if (!mounted.some((component) => component.sourceId === state.activeSourceId)) {\n state.activeSourceId = mounted[0]?.sourceId ?? null;\n }\n\n for (const listener of listeners) {\n listener();\n }\n };\n\n const persistSelections = (): void => {\n storage?.writeSelections(state.selections);\n };\n\n const persistShortcuts = (): void => {\n storage?.writeShortcutOverrides(state.shortcutConfig);\n };\n\n const moveActiveComponent = (direction: Direction): void => {\n const mounted = getMountedComponents(state);\n if (mounted.length === 0) {\n return;\n }\n\n const currentIndex = mounted.findIndex((component) => component.sourceId === state.activeSourceId);\n const current = currentIndex >= 0 ? currentIndex : 0;\n const next = rotate(mounted, current, direction);\n if (!next) {\n return;\n }\n\n state.activeSourceId = next.sourceId;\n emit();\n };\n\n const moveVariant = (direction: Direction): void => {\n const active = getActiveMountedComponent(state);\n if (!active) {\n return;\n }\n\n const currentIndex = active.variantNames.findIndex(\n (variantName) => variantName === (state.selections[active.sourceId] ?? active.selected),\n );\n const current = currentIndex >= 0 ? currentIndex : 0;\n const next = rotate(active.variantNames, current, direction);\n if (!next) {\n return;\n }\n\n state.selections[active.sourceId] = next;\n persistSelections();\n emit();\n };\n\n return {\n define(definition) {\n const existing = definitions.get(definition.sourceId);\n definitions.set(definition.sourceId, {\n ...definition,\n displayName: definition.displayName || existing?.displayName || definition.sourceId,\n variantNames: Array.from(\n new Set([...(existing?.variantNames ?? []), ...definition.variantNames]),\n ),\n });\n\n if (!(definition.sourceId in state.selections)) {\n state.selections[definition.sourceId] = definition.selected;\n persistSelections();\n }\n\n emit();\n },\n mount(sourceId) {\n mountedCounts.set(sourceId, (mountedCounts.get(sourceId) ?? 0) + 1);\n emit();\n return () => {\n mountedCounts.set(sourceId, Math.max((mountedCounts.get(sourceId) ?? 1) - 1, 0));\n emit();\n };\n },\n getSelectedVariant(sourceId, fallback) {\n return state.selections[sourceId] ?? fallback;\n },\n getSnapshot() {\n return {\n overlayOpen: state.overlayOpen,\n activeSourceId: state.activeSourceId,\n components: state.components,\n selections: state.selections,\n shortcutConfig: state.shortcutConfig,\n };\n },\n subscribe(listener) {\n listeners.add(listener);\n return () => {\n listeners.delete(listener);\n };\n },\n actions: {\n toggleOverlay() {\n state.overlayOpen = !state.overlayOpen;\n emit();\n },\n closeOverlay() {\n if (!state.overlayOpen) {\n return;\n }\n\n state.overlayOpen = false;\n emit();\n },\n nextComponent() {\n moveActiveComponent(1);\n },\n previousComponent() {\n moveActiveComponent(-1);\n },\n nextVariant() {\n moveVariant(1);\n },\n previousVariant() {\n moveVariant(-1);\n },\n selectComponent(sourceId) {\n state.activeSourceId = sourceId;\n emit();\n },\n selectVariant(sourceId, variantName) {\n const component = state.components.find((candidate) => candidate.sourceId === sourceId);\n if (!component || !component.variantNames.includes(variantName)) {\n return;\n }\n\n state.selections[sourceId] = variantName;\n persistSelections();\n emit();\n },\n configureShortcuts(overrides) {\n if (!overrides) {\n return;\n }\n\n state.shortcutConfig = {\n ...state.shortcutConfig,\n ...overrides,\n };\n persistShortcuts();\n emit();\n },\n },\n };\n}\n","import {\n createVariantRuntimeController,\n type VariantRuntimeController,\n type VariantRuntimeStorage,\n} from \"./runtime-core\";\n\nconst storageKey = \"variant:component-selections\";\nconst shortcutStorageKey = \"variant:shortcut-config\";\nconst globalControllerKey = \"__variant_runtime_controller__\";\n\nfunction createBrowserStorage(): VariantRuntimeStorage {\n return {\n readSelections() {\n if (typeof window === \"undefined\") {\n return {};\n }\n\n try {\n const raw = window.localStorage.getItem(storageKey);\n return raw ? (JSON.parse(raw) as Record<string, string>) : {};\n } catch {\n return {};\n }\n },\n writeSelections(selections) {\n if (typeof window === \"undefined\") {\n return;\n }\n\n window.localStorage.setItem(storageKey, JSON.stringify(selections));\n },\n readShortcutOverrides() {\n if (typeof window === \"undefined\") {\n return {};\n }\n\n try {\n const raw = window.localStorage.getItem(shortcutStorageKey);\n return raw ? JSON.parse(raw) : {};\n } catch {\n return {};\n }\n },\n writeShortcutOverrides(shortcutConfig) {\n if (typeof window === \"undefined\") {\n return;\n }\n\n window.localStorage.setItem(shortcutStorageKey, JSON.stringify(shortcutConfig));\n },\n };\n}\n\nexport function getVariantRuntimeController(): VariantRuntimeController {\n const target = globalThis as typeof globalThis & {\n [globalControllerKey]?: VariantRuntimeController;\n };\n\n if (!target[globalControllerKey]) {\n target[globalControllerKey] = createVariantRuntimeController({\n storage: createBrowserStorage(),\n });\n }\n\n return target[globalControllerKey]!;\n}\n","import type { RuntimeState, VariantShortcutConfig } from \"./runtime-core\";\nimport { installVariantKeyboardBindings, installVariantOverlayUi } from \"./runtime-dom\";\nimport { getVariantRuntimeController } from \"./runtime-singleton\";\n\nexport function installVariantOverlay(shortcuts?: Partial<VariantShortcutConfig>): void {\n const controller = getVariantRuntimeController();\n controller.actions.configureShortcuts(shortcuts);\n installVariantKeyboardBindings(controller);\n installVariantOverlayUi(controller);\n}\n\nexport function setVariantShortcuts(shortcuts: Partial<VariantShortcutConfig>): void {\n getVariantRuntimeController().actions.configureShortcuts(shortcuts);\n}\n\nexport function getVariantRuntimeState(): RuntimeState {\n const state = getVariantRuntimeController().getSnapshot();\n return {\n overlayOpen: state.overlayOpen,\n activeSourceId: state.activeSourceId,\n components: state.components,\n };\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
type Shortcut = string | string[];
|
|
2
|
+
type VariantShortcutConfig = {
|
|
3
|
+
toggleOverlay: Shortcut;
|
|
4
|
+
nextComponent: Shortcut;
|
|
5
|
+
previousComponent: Shortcut;
|
|
6
|
+
nextVariant: Shortcut;
|
|
7
|
+
previousVariant: Shortcut;
|
|
8
|
+
closeOverlay: Shortcut;
|
|
9
|
+
};
|
|
10
|
+
declare const defaultShortcuts: VariantShortcutConfig;
|
|
11
|
+
type VariantDefinition = {
|
|
12
|
+
sourceId: string;
|
|
13
|
+
displayName: string;
|
|
14
|
+
selected: string;
|
|
15
|
+
variantNames: string[];
|
|
16
|
+
};
|
|
17
|
+
type RuntimeComponentRecord = VariantDefinition & {
|
|
18
|
+
mountedCount: number;
|
|
19
|
+
};
|
|
20
|
+
type RuntimeState = {
|
|
21
|
+
overlayOpen: boolean;
|
|
22
|
+
activeSourceId: string | null;
|
|
23
|
+
components: RuntimeComponentRecord[];
|
|
24
|
+
};
|
|
25
|
+
type VariantRuntimeSnapshot = RuntimeState & {
|
|
26
|
+
selections: Record<string, string>;
|
|
27
|
+
shortcutConfig: VariantShortcutConfig;
|
|
28
|
+
};
|
|
29
|
+
type VariantRuntimeStorage = {
|
|
30
|
+
readSelections: () => Record<string, string>;
|
|
31
|
+
writeSelections: (selections: Record<string, string>) => void;
|
|
32
|
+
readShortcutOverrides: () => Partial<VariantShortcutConfig>;
|
|
33
|
+
writeShortcutOverrides: (shortcutConfig: VariantShortcutConfig) => void;
|
|
34
|
+
};
|
|
35
|
+
type VariantRuntimeController = {
|
|
36
|
+
define: (definition: VariantDefinition) => void;
|
|
37
|
+
mount: (sourceId: string) => () => void;
|
|
38
|
+
getSelectedVariant: (sourceId: string, fallback: string) => string;
|
|
39
|
+
getSnapshot: () => VariantRuntimeSnapshot;
|
|
40
|
+
subscribe: (listener: () => void) => () => void;
|
|
41
|
+
actions: {
|
|
42
|
+
toggleOverlay: () => void;
|
|
43
|
+
closeOverlay: () => void;
|
|
44
|
+
nextComponent: () => void;
|
|
45
|
+
previousComponent: () => void;
|
|
46
|
+
nextVariant: () => void;
|
|
47
|
+
previousVariant: () => void;
|
|
48
|
+
selectComponent: (sourceId: string | null) => void;
|
|
49
|
+
selectVariant: (sourceId: string, variantName: string) => void;
|
|
50
|
+
configureShortcuts: (overrides?: Partial<VariantShortcutConfig>) => void;
|
|
51
|
+
};
|
|
52
|
+
};
|
|
53
|
+
declare function createVariantRuntimeController(options?: {
|
|
54
|
+
storage?: VariantRuntimeStorage;
|
|
55
|
+
}): VariantRuntimeController;
|
|
56
|
+
|
|
57
|
+
declare function installVariantOverlay(shortcuts?: Partial<VariantShortcutConfig>): void;
|
|
58
|
+
declare function setVariantShortcuts(shortcuts: Partial<VariantShortcutConfig>): void;
|
|
59
|
+
declare function getVariantRuntimeState(): RuntimeState;
|
|
60
|
+
|
|
61
|
+
export { type RuntimeState as R, type Shortcut as S, type VariantDefinition as V, type VariantRuntimeController as a, type VariantRuntimeSnapshot as b, type VariantRuntimeStorage as c, type VariantShortcutConfig as d, createVariantRuntimeController as e, defaultShortcuts as f, type RuntimeComponentRecord as g, getVariantRuntimeState as h, installVariantOverlay as i, setVariantShortcuts as s };
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { ComponentType } from 'react';
|
|
2
|
+
export { g as RuntimeComponentRecord, R as RuntimeState, S as Shortcut, V as VariantDefinition, a as VariantRuntimeController, b as VariantRuntimeSnapshot, c as VariantRuntimeStorage, d as VariantShortcutConfig, e as createVariantRuntimeController, f as defaultShortcuts, h as getVariantRuntimeState, i as installVariantOverlay, s as setVariantShortcuts } from './runtime-api-CpNtY-8z.js';
|
|
3
|
+
|
|
4
|
+
type ProxyDefinition<Props extends object> = {
|
|
5
|
+
sourceId: string;
|
|
6
|
+
displayName: string;
|
|
7
|
+
selected: string;
|
|
8
|
+
variants: Record<string, ComponentType<Props>>;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
declare function createVariantProxy<Props extends object>({ sourceId, displayName, selected, variants, }: ProxyDefinition<Props>): ComponentType<Props>;
|
|
12
|
+
|
|
13
|
+
export { type ProxyDefinition, createVariantProxy };
|
package/dist/runtime.js
ADDED
|
@@ -0,0 +1,488 @@
|
|
|
1
|
+
// src/runtime.tsx
|
|
2
|
+
import {
|
|
3
|
+
useEffect,
|
|
4
|
+
useMemo,
|
|
5
|
+
useSyncExternalStore
|
|
6
|
+
} from "react";
|
|
7
|
+
|
|
8
|
+
// src/runtime-dom.ts
|
|
9
|
+
var installedKeyboardControllers = /* @__PURE__ */ new WeakSet();
|
|
10
|
+
var installedOverlayControllers = /* @__PURE__ */ new WeakSet();
|
|
11
|
+
function normalizeKey(key) {
|
|
12
|
+
const normalized = key.toLowerCase();
|
|
13
|
+
if (normalized === "esc") {
|
|
14
|
+
return "escape";
|
|
15
|
+
}
|
|
16
|
+
return normalized;
|
|
17
|
+
}
|
|
18
|
+
function parseShortcut(shortcut) {
|
|
19
|
+
const tokens = shortcut.split("+").map((token) => token.trim().toLowerCase()).filter(Boolean);
|
|
20
|
+
const key = tokens[tokens.length - 1] ?? "";
|
|
21
|
+
const modifiers = new Set(tokens.slice(0, -1));
|
|
22
|
+
return {
|
|
23
|
+
key: normalizeKey(key),
|
|
24
|
+
alt: modifiers.has("alt") || modifiers.has("option"),
|
|
25
|
+
ctrl: modifiers.has("ctrl") || modifiers.has("control"),
|
|
26
|
+
meta: modifiers.has("meta") || modifiers.has("cmd") || modifiers.has("command"),
|
|
27
|
+
shift: modifiers.has("shift")
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
function matchesShortcut(event, shortcut) {
|
|
31
|
+
const shortcuts = Array.isArray(shortcut) ? shortcut : [shortcut];
|
|
32
|
+
return shortcuts.some((candidate) => {
|
|
33
|
+
const parsed = parseShortcut(candidate);
|
|
34
|
+
return normalizeKey(event.key) === parsed.key && event.altKey === parsed.alt && event.ctrlKey === parsed.ctrl && event.metaKey === parsed.meta && event.shiftKey === parsed.shift;
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
function isEditableTarget(target) {
|
|
38
|
+
if (!(target instanceof HTMLElement)) {
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
const tagName = target.tagName.toLowerCase();
|
|
42
|
+
return target.isContentEditable || tagName === "input" || tagName === "textarea" || tagName === "select";
|
|
43
|
+
}
|
|
44
|
+
function installVariantKeyboardBindings(controller) {
|
|
45
|
+
if (installedKeyboardControllers.has(controller) || typeof window === "undefined") {
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
const onKeyDown = (event) => {
|
|
49
|
+
if (isEditableTarget(event.target)) {
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
const { shortcutConfig, overlayOpen } = controller.getSnapshot();
|
|
53
|
+
if (matchesShortcut(event, shortcutConfig.toggleOverlay)) {
|
|
54
|
+
event.preventDefault();
|
|
55
|
+
controller.actions.toggleOverlay();
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
if (matchesShortcut(event, shortcutConfig.closeOverlay)) {
|
|
59
|
+
if (overlayOpen) {
|
|
60
|
+
event.preventDefault();
|
|
61
|
+
controller.actions.closeOverlay();
|
|
62
|
+
}
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
if (matchesShortcut(event, shortcutConfig.nextComponent)) {
|
|
66
|
+
event.preventDefault();
|
|
67
|
+
controller.actions.nextComponent();
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
if (matchesShortcut(event, shortcutConfig.previousComponent)) {
|
|
71
|
+
event.preventDefault();
|
|
72
|
+
controller.actions.previousComponent();
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
if (matchesShortcut(event, shortcutConfig.nextVariant)) {
|
|
76
|
+
event.preventDefault();
|
|
77
|
+
controller.actions.nextVariant();
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
if (matchesShortcut(event, shortcutConfig.previousVariant)) {
|
|
81
|
+
event.preventDefault();
|
|
82
|
+
controller.actions.previousVariant();
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
window.addEventListener("keydown", onKeyDown);
|
|
86
|
+
installedKeyboardControllers.add(controller);
|
|
87
|
+
}
|
|
88
|
+
function installVariantOverlayUi(controller) {
|
|
89
|
+
if (installedOverlayControllers.has(controller) || typeof document === "undefined") {
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
const container = document.createElement("div");
|
|
93
|
+
container.setAttribute("data-variant-overlay-root", "true");
|
|
94
|
+
document.body.appendChild(container);
|
|
95
|
+
const render = () => {
|
|
96
|
+
renderOverlay(container, controller.getSnapshot(), controller);
|
|
97
|
+
};
|
|
98
|
+
controller.subscribe(render);
|
|
99
|
+
installedOverlayControllers.add(controller);
|
|
100
|
+
render();
|
|
101
|
+
}
|
|
102
|
+
function renderOverlay(container, snapshot, controller) {
|
|
103
|
+
if (!snapshot.overlayOpen) {
|
|
104
|
+
container.innerHTML = "";
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
const mounted = snapshot.components.filter((component) => component.mountedCount > 0);
|
|
108
|
+
const active = mounted.find((component) => component.sourceId === snapshot.activeSourceId) ?? mounted[0] ?? null;
|
|
109
|
+
const activeSelection = active ? snapshot.selections[active.sourceId] ?? active.selected : null;
|
|
110
|
+
const componentOptions = mounted.map((component) => {
|
|
111
|
+
const selected = component.sourceId === active?.sourceId;
|
|
112
|
+
return `<option value="${escapeHtml(component.sourceId)}"${selected ? " selected" : ""}>${escapeHtml(
|
|
113
|
+
component.displayName
|
|
114
|
+
)}</option>`;
|
|
115
|
+
}).join("");
|
|
116
|
+
const variantOptions = active ? active.variantNames.map((variantName) => {
|
|
117
|
+
const selected = activeSelection === variantName;
|
|
118
|
+
return `<option value="${escapeHtml(variantName)}"${selected ? " selected" : ""}>${escapeHtml(
|
|
119
|
+
variantName
|
|
120
|
+
)}</option>`;
|
|
121
|
+
}).join("") : "";
|
|
122
|
+
container.innerHTML = `
|
|
123
|
+
<div style="${hudShellStyle()}">
|
|
124
|
+
<div style="${panelStyle()}">
|
|
125
|
+
<select data-variant-active-source="true" style="${selectStyle()}" ${mounted.length === 0 ? "disabled" : ""}>
|
|
126
|
+
${componentOptions || `<option value="">No mounted components</option>`}
|
|
127
|
+
</select>
|
|
128
|
+
<select data-variant-active-choice="true" style="${selectStyle()}" ${!active ? "disabled" : ""}>
|
|
129
|
+
${variantOptions || `<option value="">No variants</option>`}
|
|
130
|
+
</select>
|
|
131
|
+
</div>
|
|
132
|
+
</div>`;
|
|
133
|
+
container.querySelector('[data-variant-active-source="true"]')?.addEventListener("change", (event) => {
|
|
134
|
+
const target = event.currentTarget;
|
|
135
|
+
controller.actions.selectComponent(target.value || null);
|
|
136
|
+
});
|
|
137
|
+
container.querySelector('[data-variant-active-choice="true"]')?.addEventListener("change", (event) => {
|
|
138
|
+
const target = event.currentTarget;
|
|
139
|
+
if (!active || !target.value) {
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
controller.actions.selectVariant(active.sourceId, target.value);
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
function escapeHtml(value) {
|
|
146
|
+
return value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """);
|
|
147
|
+
}
|
|
148
|
+
function hudShellStyle() {
|
|
149
|
+
return [
|
|
150
|
+
"position:fixed",
|
|
151
|
+
"top:16px",
|
|
152
|
+
"right:16px",
|
|
153
|
+
"z-index:9999",
|
|
154
|
+
"pointer-events:none",
|
|
155
|
+
"max-width:500px"
|
|
156
|
+
].join(";");
|
|
157
|
+
}
|
|
158
|
+
function panelStyle() {
|
|
159
|
+
return [
|
|
160
|
+
"display:flex",
|
|
161
|
+
"align-items:center",
|
|
162
|
+
"gap:8px",
|
|
163
|
+
"height:48px",
|
|
164
|
+
"width:100%",
|
|
165
|
+
"background:rgba(255,255,255,0.98)",
|
|
166
|
+
"backdrop-filter:blur(12px)",
|
|
167
|
+
"box-shadow:0 18px 40px rgba(15,23,42,0.16)",
|
|
168
|
+
"border:1px solid rgba(148,163,184,0.35)",
|
|
169
|
+
"border-radius:14px",
|
|
170
|
+
"padding:8px",
|
|
171
|
+
"pointer-events:auto",
|
|
172
|
+
"overflow:hidden",
|
|
173
|
+
'font-family:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif',
|
|
174
|
+
"color:#0f172a"
|
|
175
|
+
].join(";");
|
|
176
|
+
}
|
|
177
|
+
function selectStyle() {
|
|
178
|
+
return [
|
|
179
|
+
"flex:1",
|
|
180
|
+
"min-width:0",
|
|
181
|
+
"max-width:100%",
|
|
182
|
+
"border:1px solid #cbd5e1",
|
|
183
|
+
"height:32px",
|
|
184
|
+
"border-radius:10px",
|
|
185
|
+
"background:#fff",
|
|
186
|
+
"color:#0f172a",
|
|
187
|
+
"padding:0 10px",
|
|
188
|
+
"font-size:13px",
|
|
189
|
+
"outline:none"
|
|
190
|
+
].join(";");
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// src/runtime-core.ts
|
|
194
|
+
var defaultShortcuts = {
|
|
195
|
+
toggleOverlay: ["meta+shift+.", "ctrl+shift+."],
|
|
196
|
+
nextComponent: ["meta+alt+arrowdown", "ctrl+alt+arrowdown"],
|
|
197
|
+
previousComponent: ["meta+alt+arrowup", "ctrl+alt+arrowup"],
|
|
198
|
+
nextVariant: ["meta+shift+arrowright", "ctrl+shift+arrowright"],
|
|
199
|
+
previousVariant: ["meta+shift+arrowleft", "ctrl+shift+arrowleft"],
|
|
200
|
+
closeOverlay: "escape"
|
|
201
|
+
};
|
|
202
|
+
function rotate(items, currentIndex, direction) {
|
|
203
|
+
if (items.length === 0) {
|
|
204
|
+
return null;
|
|
205
|
+
}
|
|
206
|
+
const nextIndex = (currentIndex + direction + items.length) % items.length;
|
|
207
|
+
return items[nextIndex] ?? null;
|
|
208
|
+
}
|
|
209
|
+
function getMountedComponents(state) {
|
|
210
|
+
return state.components.filter((component) => component.mountedCount > 0);
|
|
211
|
+
}
|
|
212
|
+
function getActiveMountedComponent(state) {
|
|
213
|
+
const mounted = getMountedComponents(state);
|
|
214
|
+
return mounted.find((component) => component.sourceId === state.activeSourceId) ?? mounted[0] ?? null;
|
|
215
|
+
}
|
|
216
|
+
function createVariantRuntimeController(options = {}) {
|
|
217
|
+
const storage = options.storage;
|
|
218
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
219
|
+
const definitions = /* @__PURE__ */ new Map();
|
|
220
|
+
const mountedCounts = /* @__PURE__ */ new Map();
|
|
221
|
+
const state = {
|
|
222
|
+
overlayOpen: false,
|
|
223
|
+
activeSourceId: null,
|
|
224
|
+
components: [],
|
|
225
|
+
selections: storage?.readSelections() ?? {},
|
|
226
|
+
shortcutConfig: {
|
|
227
|
+
...defaultShortcuts,
|
|
228
|
+
...storage?.readShortcutOverrides() ?? {}
|
|
229
|
+
}
|
|
230
|
+
};
|
|
231
|
+
const emit = () => {
|
|
232
|
+
state.components = Array.from(definitions.values()).map((definition) => ({
|
|
233
|
+
...definition,
|
|
234
|
+
selected: state.selections[definition.sourceId] ?? definition.selected,
|
|
235
|
+
mountedCount: mountedCounts.get(definition.sourceId) ?? 0
|
|
236
|
+
})).sort((left, right) => left.displayName.localeCompare(right.displayName));
|
|
237
|
+
const mounted = getMountedComponents(state);
|
|
238
|
+
if (!mounted.some((component) => component.sourceId === state.activeSourceId)) {
|
|
239
|
+
state.activeSourceId = mounted[0]?.sourceId ?? null;
|
|
240
|
+
}
|
|
241
|
+
for (const listener of listeners) {
|
|
242
|
+
listener();
|
|
243
|
+
}
|
|
244
|
+
};
|
|
245
|
+
const persistSelections = () => {
|
|
246
|
+
storage?.writeSelections(state.selections);
|
|
247
|
+
};
|
|
248
|
+
const persistShortcuts = () => {
|
|
249
|
+
storage?.writeShortcutOverrides(state.shortcutConfig);
|
|
250
|
+
};
|
|
251
|
+
const moveActiveComponent = (direction) => {
|
|
252
|
+
const mounted = getMountedComponents(state);
|
|
253
|
+
if (mounted.length === 0) {
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
const currentIndex = mounted.findIndex((component) => component.sourceId === state.activeSourceId);
|
|
257
|
+
const current = currentIndex >= 0 ? currentIndex : 0;
|
|
258
|
+
const next = rotate(mounted, current, direction);
|
|
259
|
+
if (!next) {
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
state.activeSourceId = next.sourceId;
|
|
263
|
+
emit();
|
|
264
|
+
};
|
|
265
|
+
const moveVariant = (direction) => {
|
|
266
|
+
const active = getActiveMountedComponent(state);
|
|
267
|
+
if (!active) {
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
const currentIndex = active.variantNames.findIndex(
|
|
271
|
+
(variantName) => variantName === (state.selections[active.sourceId] ?? active.selected)
|
|
272
|
+
);
|
|
273
|
+
const current = currentIndex >= 0 ? currentIndex : 0;
|
|
274
|
+
const next = rotate(active.variantNames, current, direction);
|
|
275
|
+
if (!next) {
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
278
|
+
state.selections[active.sourceId] = next;
|
|
279
|
+
persistSelections();
|
|
280
|
+
emit();
|
|
281
|
+
};
|
|
282
|
+
return {
|
|
283
|
+
define(definition) {
|
|
284
|
+
const existing = definitions.get(definition.sourceId);
|
|
285
|
+
definitions.set(definition.sourceId, {
|
|
286
|
+
...definition,
|
|
287
|
+
displayName: definition.displayName || existing?.displayName || definition.sourceId,
|
|
288
|
+
variantNames: Array.from(
|
|
289
|
+
/* @__PURE__ */ new Set([...existing?.variantNames ?? [], ...definition.variantNames])
|
|
290
|
+
)
|
|
291
|
+
});
|
|
292
|
+
if (!(definition.sourceId in state.selections)) {
|
|
293
|
+
state.selections[definition.sourceId] = definition.selected;
|
|
294
|
+
persistSelections();
|
|
295
|
+
}
|
|
296
|
+
emit();
|
|
297
|
+
},
|
|
298
|
+
mount(sourceId) {
|
|
299
|
+
mountedCounts.set(sourceId, (mountedCounts.get(sourceId) ?? 0) + 1);
|
|
300
|
+
emit();
|
|
301
|
+
return () => {
|
|
302
|
+
mountedCounts.set(sourceId, Math.max((mountedCounts.get(sourceId) ?? 1) - 1, 0));
|
|
303
|
+
emit();
|
|
304
|
+
};
|
|
305
|
+
},
|
|
306
|
+
getSelectedVariant(sourceId, fallback) {
|
|
307
|
+
return state.selections[sourceId] ?? fallback;
|
|
308
|
+
},
|
|
309
|
+
getSnapshot() {
|
|
310
|
+
return {
|
|
311
|
+
overlayOpen: state.overlayOpen,
|
|
312
|
+
activeSourceId: state.activeSourceId,
|
|
313
|
+
components: state.components,
|
|
314
|
+
selections: state.selections,
|
|
315
|
+
shortcutConfig: state.shortcutConfig
|
|
316
|
+
};
|
|
317
|
+
},
|
|
318
|
+
subscribe(listener) {
|
|
319
|
+
listeners.add(listener);
|
|
320
|
+
return () => {
|
|
321
|
+
listeners.delete(listener);
|
|
322
|
+
};
|
|
323
|
+
},
|
|
324
|
+
actions: {
|
|
325
|
+
toggleOverlay() {
|
|
326
|
+
state.overlayOpen = !state.overlayOpen;
|
|
327
|
+
emit();
|
|
328
|
+
},
|
|
329
|
+
closeOverlay() {
|
|
330
|
+
if (!state.overlayOpen) {
|
|
331
|
+
return;
|
|
332
|
+
}
|
|
333
|
+
state.overlayOpen = false;
|
|
334
|
+
emit();
|
|
335
|
+
},
|
|
336
|
+
nextComponent() {
|
|
337
|
+
moveActiveComponent(1);
|
|
338
|
+
},
|
|
339
|
+
previousComponent() {
|
|
340
|
+
moveActiveComponent(-1);
|
|
341
|
+
},
|
|
342
|
+
nextVariant() {
|
|
343
|
+
moveVariant(1);
|
|
344
|
+
},
|
|
345
|
+
previousVariant() {
|
|
346
|
+
moveVariant(-1);
|
|
347
|
+
},
|
|
348
|
+
selectComponent(sourceId) {
|
|
349
|
+
state.activeSourceId = sourceId;
|
|
350
|
+
emit();
|
|
351
|
+
},
|
|
352
|
+
selectVariant(sourceId, variantName) {
|
|
353
|
+
const component = state.components.find((candidate) => candidate.sourceId === sourceId);
|
|
354
|
+
if (!component || !component.variantNames.includes(variantName)) {
|
|
355
|
+
return;
|
|
356
|
+
}
|
|
357
|
+
state.selections[sourceId] = variantName;
|
|
358
|
+
persistSelections();
|
|
359
|
+
emit();
|
|
360
|
+
},
|
|
361
|
+
configureShortcuts(overrides) {
|
|
362
|
+
if (!overrides) {
|
|
363
|
+
return;
|
|
364
|
+
}
|
|
365
|
+
state.shortcutConfig = {
|
|
366
|
+
...state.shortcutConfig,
|
|
367
|
+
...overrides
|
|
368
|
+
};
|
|
369
|
+
persistShortcuts();
|
|
370
|
+
emit();
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
};
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
// src/runtime-singleton.ts
|
|
377
|
+
var storageKey = "variant:component-selections";
|
|
378
|
+
var shortcutStorageKey = "variant:shortcut-config";
|
|
379
|
+
var globalControllerKey = "__variant_runtime_controller__";
|
|
380
|
+
function createBrowserStorage() {
|
|
381
|
+
return {
|
|
382
|
+
readSelections() {
|
|
383
|
+
if (typeof window === "undefined") {
|
|
384
|
+
return {};
|
|
385
|
+
}
|
|
386
|
+
try {
|
|
387
|
+
const raw = window.localStorage.getItem(storageKey);
|
|
388
|
+
return raw ? JSON.parse(raw) : {};
|
|
389
|
+
} catch {
|
|
390
|
+
return {};
|
|
391
|
+
}
|
|
392
|
+
},
|
|
393
|
+
writeSelections(selections) {
|
|
394
|
+
if (typeof window === "undefined") {
|
|
395
|
+
return;
|
|
396
|
+
}
|
|
397
|
+
window.localStorage.setItem(storageKey, JSON.stringify(selections));
|
|
398
|
+
},
|
|
399
|
+
readShortcutOverrides() {
|
|
400
|
+
if (typeof window === "undefined") {
|
|
401
|
+
return {};
|
|
402
|
+
}
|
|
403
|
+
try {
|
|
404
|
+
const raw = window.localStorage.getItem(shortcutStorageKey);
|
|
405
|
+
return raw ? JSON.parse(raw) : {};
|
|
406
|
+
} catch {
|
|
407
|
+
return {};
|
|
408
|
+
}
|
|
409
|
+
},
|
|
410
|
+
writeShortcutOverrides(shortcutConfig) {
|
|
411
|
+
if (typeof window === "undefined") {
|
|
412
|
+
return;
|
|
413
|
+
}
|
|
414
|
+
window.localStorage.setItem(shortcutStorageKey, JSON.stringify(shortcutConfig));
|
|
415
|
+
}
|
|
416
|
+
};
|
|
417
|
+
}
|
|
418
|
+
function getVariantRuntimeController() {
|
|
419
|
+
const target = globalThis;
|
|
420
|
+
if (!target[globalControllerKey]) {
|
|
421
|
+
target[globalControllerKey] = createVariantRuntimeController({
|
|
422
|
+
storage: createBrowserStorage()
|
|
423
|
+
});
|
|
424
|
+
}
|
|
425
|
+
return target[globalControllerKey];
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
// src/runtime-api.ts
|
|
429
|
+
function installVariantOverlay(shortcuts) {
|
|
430
|
+
const controller = getVariantRuntimeController();
|
|
431
|
+
controller.actions.configureShortcuts(shortcuts);
|
|
432
|
+
installVariantKeyboardBindings(controller);
|
|
433
|
+
installVariantOverlayUi(controller);
|
|
434
|
+
}
|
|
435
|
+
function setVariantShortcuts(shortcuts) {
|
|
436
|
+
getVariantRuntimeController().actions.configureShortcuts(shortcuts);
|
|
437
|
+
}
|
|
438
|
+
function getVariantRuntimeState() {
|
|
439
|
+
const state = getVariantRuntimeController().getSnapshot();
|
|
440
|
+
return {
|
|
441
|
+
overlayOpen: state.overlayOpen,
|
|
442
|
+
activeSourceId: state.activeSourceId,
|
|
443
|
+
components: state.components
|
|
444
|
+
};
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
// src/runtime.tsx
|
|
448
|
+
import { jsx } from "react/jsx-runtime";
|
|
449
|
+
function createVariantProxy({
|
|
450
|
+
sourceId,
|
|
451
|
+
displayName,
|
|
452
|
+
selected,
|
|
453
|
+
variants
|
|
454
|
+
}) {
|
|
455
|
+
const controller = getVariantRuntimeController();
|
|
456
|
+
const variantNames = Object.keys(variants);
|
|
457
|
+
const initialSelected = variantNames.includes(selected) ? selected : "source";
|
|
458
|
+
controller.define({
|
|
459
|
+
sourceId,
|
|
460
|
+
displayName,
|
|
461
|
+
selected: initialSelected,
|
|
462
|
+
variantNames
|
|
463
|
+
});
|
|
464
|
+
function VariantProxy(props) {
|
|
465
|
+
const currentVariant = useSyncExternalStore(
|
|
466
|
+
controller.subscribe,
|
|
467
|
+
() => controller.getSelectedVariant(sourceId, initialSelected),
|
|
468
|
+
() => initialSelected
|
|
469
|
+
);
|
|
470
|
+
useEffect(() => controller.mount(sourceId), []);
|
|
471
|
+
const Component = useMemo(
|
|
472
|
+
() => variants[currentVariant] ?? variants[initialSelected] ?? variants.source,
|
|
473
|
+
[currentVariant]
|
|
474
|
+
);
|
|
475
|
+
return /* @__PURE__ */ jsx(Component, { ...props });
|
|
476
|
+
}
|
|
477
|
+
VariantProxy.displayName = `${displayName.replace(/\s+/g, "")}VariantProxy`;
|
|
478
|
+
return VariantProxy;
|
|
479
|
+
}
|
|
480
|
+
export {
|
|
481
|
+
createVariantProxy,
|
|
482
|
+
createVariantRuntimeController,
|
|
483
|
+
defaultShortcuts,
|
|
484
|
+
getVariantRuntimeState,
|
|
485
|
+
installVariantOverlay,
|
|
486
|
+
setVariantShortcuts
|
|
487
|
+
};
|
|
488
|
+
//# sourceMappingURL=runtime.js.map
|