flexdesk 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.
Files changed (124) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +195 -0
  3. package/THIRD_PARTY_LICENSES.md +53 -0
  4. package/css/base.css +3439 -0
  5. package/css/flexdesk.css +5797 -0
  6. package/css/overrides.css +2349 -0
  7. package/css/reset.css +46 -0
  8. package/css/tokens.css +137 -0
  9. package/dist/charts.js +780 -0
  10. package/dist/charts.js.map +7 -0
  11. package/dist/chunk-3PHCPZHT.js +222 -0
  12. package/dist/chunk-3PHCPZHT.js.map +7 -0
  13. package/dist/chunk-CT4YXXLP.js +1770 -0
  14. package/dist/chunk-CT4YXXLP.js.map +7 -0
  15. package/dist/chunk-DRYCDMEG.js +2217 -0
  16. package/dist/chunk-DRYCDMEG.js.map +7 -0
  17. package/dist/chunk-DVU44T77.js +855 -0
  18. package/dist/chunk-DVU44T77.js.map +7 -0
  19. package/dist/chunk-FL5KFNQH.js +640 -0
  20. package/dist/chunk-FL5KFNQH.js.map +7 -0
  21. package/dist/chunk-FOOS3T5L.js +117 -0
  22. package/dist/chunk-FOOS3T5L.js.map +7 -0
  23. package/dist/chunk-JYWURG5T.js +32 -0
  24. package/dist/chunk-JYWURG5T.js.map +7 -0
  25. package/dist/chunk-TLZUUFOE.js +276 -0
  26. package/dist/chunk-TLZUUFOE.js.map +7 -0
  27. package/dist/chunk-UCJ2WD4D.js +625 -0
  28. package/dist/chunk-UCJ2WD4D.js.map +7 -0
  29. package/dist/chunk-WVFGV5FT.js +205 -0
  30. package/dist/chunk-WVFGV5FT.js.map +7 -0
  31. package/dist/core.js +1098 -0
  32. package/dist/core.js.map +7 -0
  33. package/dist/editor.js +1661 -0
  34. package/dist/editor.js.map +7 -0
  35. package/dist/flexdesk.css +5797 -0
  36. package/dist/host.js +132 -0
  37. package/dist/host.js.map +7 -0
  38. package/dist/reset.css +46 -0
  39. package/dist/tile_registry-6WZPTUZV.js +28 -0
  40. package/dist/tile_registry-6WZPTUZV.js.map +7 -0
  41. package/dist/tiles.js +2068 -0
  42. package/dist/tiles.js.map +7 -0
  43. package/dist/tokens.css +137 -0
  44. package/dist/widgets.js +5438 -0
  45. package/dist/widgets.js.map +7 -0
  46. package/dist/wm.js +4830 -0
  47. package/dist/wm.js.map +7 -0
  48. package/package.json +80 -0
  49. package/src/charts/chart_types.js +699 -0
  50. package/src/charts/data_series_plot_window.js +761 -0
  51. package/src/charts/downsample.js +119 -0
  52. package/src/charts/plot_config.js +176 -0
  53. package/src/charts/plot_popout_window.js +1560 -0
  54. package/src/charts/plotly_wrapper.js +432 -0
  55. package/src/core/event_bus.js +230 -0
  56. package/src/core/logging.js +300 -0
  57. package/src/core/settings.js +813 -0
  58. package/src/core/state_guard.js +382 -0
  59. package/src/core/state_guard_config.js +309 -0
  60. package/src/core/state_machine.js +258 -0
  61. package/src/editor/editor_pane.js +264 -0
  62. package/src/editor/monaco_editor_factory.js +449 -0
  63. package/src/editor/monaco_loader.js +74 -0
  64. package/src/editor/notebook_search.js +460 -0
  65. package/src/editor/notebook_tab_bar.js +456 -0
  66. package/src/editor/notebook_undo_manager.js +108 -0
  67. package/src/editor/split_pane_container.js +197 -0
  68. package/src/help/help_modal.js +409 -0
  69. package/src/help/help_registry.js +65 -0
  70. package/src/host/host.js +69 -0
  71. package/src/host/pywebview_host.js +133 -0
  72. package/src/tiles/config_schema.js +256 -0
  73. package/src/tiles/layout_persistence.js +662 -0
  74. package/src/tiles/tile_base.js +785 -0
  75. package/src/tiles/tile_grid.js +905 -0
  76. package/src/tiles/tile_registry.js +136 -0
  77. package/src/tiling/command_palette.js +328 -0
  78. package/src/tiling/content_registry.js +110 -0
  79. package/src/tiling/desktops.js +133 -0
  80. package/src/tiling/entity_sources.js +179 -0
  81. package/src/tiling/keymap.js +156 -0
  82. package/src/tiling/kind_taxonomy.js +172 -0
  83. package/src/tiling/landing_table.js +717 -0
  84. package/src/tiling/loading_overlay.js +33 -0
  85. package/src/tiling/nav_panel.js +142 -0
  86. package/src/tiling/page_factory.js +215 -0
  87. package/src/tiling/panel_keys.js +145 -0
  88. package/src/tiling/shell.js +437 -0
  89. package/src/tiling/tile_breadcrumb.js +222 -0
  90. package/src/tiling/tile_renderer.js +459 -0
  91. package/src/tiling/tile_tab_menu.js +340 -0
  92. package/src/tiling/tile_tree.js +880 -0
  93. package/src/tiling/wm.js +1352 -0
  94. package/src/ui/base/component_base.js +30 -0
  95. package/src/ui/base/controller_base.js +48 -0
  96. package/src/ui/base/page_base.js +102 -0
  97. package/src/ui/components/about_dialog.js +224 -0
  98. package/src/ui/components/action_dropdown.js +389 -0
  99. package/src/ui/components/attribute_list_editor.js +652 -0
  100. package/src/ui/components/autocomplete_field.js +732 -0
  101. package/src/ui/components/computing_status_window.js +81 -0
  102. package/src/ui/components/confirm_dialog.js +307 -0
  103. package/src/ui/components/context_menu.js +87 -0
  104. package/src/ui/components/data_table.js +2228 -0
  105. package/src/ui/components/detail_header.js +177 -0
  106. package/src/ui/components/drag_reorder.js +0 -0
  107. package/src/ui/components/gallery_picker.js +147 -0
  108. package/src/ui/components/inline_renamer.js +129 -0
  109. package/src/ui/components/managed_window.js +804 -0
  110. package/src/ui/components/modal.js +512 -0
  111. package/src/ui/components/notification_history.js +264 -0
  112. package/src/ui/components/slide_out_panel.js +185 -0
  113. package/src/ui/components/slider_field.js +122 -0
  114. package/src/ui/components/sortable_list.js +309 -0
  115. package/src/ui/components/table_state_store.js +81 -0
  116. package/src/ui/components/toast.js +59 -0
  117. package/src/ui/components/tree_view.js +460 -0
  118. package/src/ui/components/window_taskbar.js +143 -0
  119. package/src/ui/controllers/panel_state_machine.js +634 -0
  120. package/src/ui/controllers/window_chrome_controller.js +659 -0
  121. package/src/ui/notification_center.js +576 -0
  122. package/src/ui/utils/overlay_scrollbar.js +492 -0
  123. package/src/ui/utils/raf_resize_observer.js +31 -0
  124. package/src/ui/utils/tooltip_service.js +406 -0
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/core/settings.js"],
4
+ "sourcesContent": ["/**\n * Application Settings \u2014 a generic, schema-driven value store.\n *\n * Purpose\n * -------\n * A persisted key/value store whose UI is driven entirely by a declarative\n * SCHEMA. The store knows nothing about any particular application's settings:\n * it ships the *shell's* own namespaces (workspace, window, logging, data,\n * notebook, ai, \u2026) and an embedder PUSHES its own namespace in at boot with\n * `registerSettings()`. The Settings page renders whatever is in the schema.\n *\n * Two doors, on purpose\n * ---------------------\n * createSettingsStore({ defaults, schema, categories, storageKey })\n * The primitive. A private store with zero global state \u2014 what the\n * library's demo and the unit tests use.\n *\n * getSetting() / setSetting() / registerSettings() / \u2026\n * A thin binding over ONE instance of that primitive for THIS document.\n * The cardinality is correct, not merely convenient: the store has a\n * single physical backing key (`localStorage['ecosim.settings.v1']`), so\n * two live stores in one document would be two writers to one key.\n *\n * Usage\n * -----\n * import { getSetting, setSetting } from '../core/settings.js';\n *\n * // Read a setting\n * if (getSetting('workspace.save.showToast')) { ... }\n *\n * // Write a setting (persists to localStorage, emits events)\n * setSetting('window.macShadows', true);\n *\n * // React to changes (after registerSettingsEventBus is called)\n * eventBus.on('settings:window.macShadows:changed', ({ value }) => { ... });\n *\n * Adding New Settings\n * -------------------\n * Shell-owned setting: add the default under the right namespace in DEFAULTS,\n * add a SCHEMA entry (type, category, group, label, description), done \u2014 the\n * Settings page picks it up.\n *\n * Embedder-owned setting: do NOT add it here. Put it in your own slice and\n * `registerSettings({ defaults, schema, categories })` at bootstrap.\n */\n\n// \u2500\u2500\u2500 Defaults (shell-owned namespaces only) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nconst DEFAULTS = Object.freeze({\n workspace: Object.freeze({\n save: Object.freeze({\n showToast: false,\n showErrorToast: true,\n }),\n import: Object.freeze({\n showToast: false,\n showErrorToast: true,\n }),\n autosave: Object.freeze({\n enabled: true,\n intervalSeconds: 60,\n }),\n undoHistoryLimit: 200,\n }),\n\n host: Object.freeze({\n showConnectedToast: false,\n }),\n\n window: Object.freeze({\n animateMinimize: true,\n macShadows: true,\n }),\n\n notifications: Object.freeze({\n durationMs: 3500,\n }),\n\n data: Object.freeze({\n tablePageSize: 100,\n defaultResampleMethod: 'mean',\n }),\n\n etl: Object.freeze({\n parallelWorkers: 1,\n }),\n\n logging: Object.freeze({\n minLevel: 'info',\n historyLimit: 500,\n enableConsole: false,\n }),\n\n editor: Object.freeze({\n autosaveDelayMs: 5000,\n }),\n\n notebook: Object.freeze({\n cellWidthMode: 'fixed',\n codeCellMaxHeight: false,\n defaultCellView: 'config',\n }),\n\n debug: Object.freeze({\n logSettingsAccess: false,\n }),\n\n projects: Object.freeze({\n directory: null,\n }),\n\n // Only auto-create-defaults survives; the modules directory / addon /\n // ETL-plugin loaders were the node-graph plugin system.\n ai: Object.freeze({\n provider: '',\n cloud: Object.freeze({\n providerId: 'anthropic',\n providers: Object.freeze({}),\n }),\n local: Object.freeze({\n serverType: 'llamacpp',\n modelPath: '',\n baseUrl: '',\n model: '',\n port: 8080,\n gpuLayers: -1,\n contextLength: 32768,\n flashAttention: true,\n evalBatchSize: 512,\n kvCacheOnGpu: true,\n }),\n defaultMode: 'ask',\n maxToolCalls: 25,\n }),\n});\n\n// \u2500\u2500\u2500 Schema (drives the Settings page UI) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n/**\n * @typedef {Object} SettingDef\n * @property {'boolean'|'select'|'number'|'text'|'colorList'} type\n * @property {string} category - Category ID (matches CATEGORIES[].id)\n * @property {string} group - Visual group within the category\n * @property {string} label - Human-readable label\n * @property {string} description - Explanation shown below the label\n * @property {*} defaultValue - Must match DEFAULTS\n * @property {Array<{value:string,label:string}>} [options] - For 'select' type\n * @property {number} [min] - For 'number' type\n * @property {number} [max] - For 'number' type\n * @property {number|string} [step] - For 'number' type\n * @property {string} [placeholder] - For 'text' type\n */\n\n/**\n * @typedef {Object} SettingsSlice\n * @property {object} [defaults] - Nested default values for the namespace.\n * @property {Object<string, SettingDef>} [schema] - Dot-path -> SettingDef.\n * @property {Array<CategoryDef>} [categories] - Categories the slice adds.\n */\n\n/**\n * @typedef {Object} CategoryDef\n * @property {string} id\n * @property {string} label\n * @property {string} icon\n * @property {string} description\n * @property {number} [order] - Sidebar sort key. Lower sorts first; ties keep\n * registration order. Defaults to 1000.\n */\n\nconst SCHEMA = Object.freeze({\n // \u2500\u2500 General \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n 'workspace.save.showToast': {\n type: 'boolean', category: 'general', group: 'Workspace Notifications',\n label: 'Save success notification',\n description: 'Show a toast notification when a workspace is saved successfully.',\n defaultValue: false,\n },\n 'workspace.save.showErrorToast': {\n type: 'boolean', category: 'general', group: 'Workspace Notifications',\n label: 'Save error notification',\n description: 'Show a toast notification when a workspace save fails.',\n defaultValue: true,\n },\n 'workspace.import.showToast': {\n type: 'boolean', category: 'general', group: 'Workspace Notifications',\n label: 'Import success notification',\n description: 'Show a toast notification when a workspace is imported successfully.',\n defaultValue: false,\n },\n 'workspace.import.showErrorToast': {\n type: 'boolean', category: 'general', group: 'Workspace Notifications',\n label: 'Import error notification',\n description: 'Show a toast notification when a workspace import fails.',\n defaultValue: true,\n },\n 'host.showConnectedToast': {\n type: 'boolean', category: 'general', group: 'Host Bridge',\n label: 'Host connected notification',\n description: 'Show a toast notification when the desktop host bridge connects.',\n defaultValue: false,\n },\n 'notifications.durationMs': {\n type: 'number', category: 'general', group: 'Notifications',\n label: 'Toast notification duration',\n description: 'How long toast notifications stay visible (milliseconds).',\n defaultValue: 3500, min: 1000, max: 15000, step: 500,\n },\n 'workspace.autosave.enabled': {\n type: 'boolean', category: 'general', group: 'Auto-Save',\n label: 'Enable auto-save',\n description: 'Automatically save the workspace at regular intervals.',\n defaultValue: true,\n },\n 'workspace.autosave.intervalSeconds': {\n type: 'number', category: 'general', group: 'Auto-Save',\n label: 'Auto-save interval (seconds)',\n description: 'Time between automatic saves.',\n defaultValue: 60, min: 10, max: 600, step: 10,\n },\n 'workspace.undoHistoryLimit': {\n type: 'number', category: 'general', group: 'History',\n label: 'Undo/redo history depth',\n description: 'Maximum number of undo/redo steps retained in memory.',\n defaultValue: 200, min: 10, max: 1000, step: 10,\n },\n\n // \u2500\u2500 Windows \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n 'window.animateMinimize': {\n type: 'boolean', category: 'window', group: 'Appearance',\n label: 'Animate minimize/restore',\n description: 'Animate managed windows toward/from the taskbar when minimizing and restoring.',\n defaultValue: true,\n },\n 'window.macShadows': {\n type: 'boolean', category: 'window', group: 'Appearance',\n label: 'macOS-style shadows',\n description: 'Use multi-layered soft shadows on managed windows.',\n defaultValue: true,\n },\n\n // \u2500\u2500 Data \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n 'data.tablePageSize': {\n type: 'number', category: 'data', group: 'Tables',\n label: 'Table page size',\n description: 'Number of rows displayed per page in data tables.',\n defaultValue: 100, min: 25, max: 1000, step: 25,\n },\n 'data.defaultResampleMethod': {\n type: 'select', category: 'data', group: 'Import',\n label: 'Default resample method',\n description: 'Aggregation method used when resampling imported time series.',\n defaultValue: 'mean',\n options: [\n { value: 'mean', label: 'Mean' },\n { value: 'sum', label: 'Sum' },\n { value: 'last', label: 'Last' },\n { value: 'first', label: 'First' },\n { value: 'linear', label: 'Linear interpolation' },\n ],\n },\n 'etl.parallelWorkers': {\n type: 'number', category: 'data', group: 'ETL Pipelines',\n label: 'Parallel pipeline workers',\n description: 'Number of pipelines to execute simultaneously in orchestrations. 1 = sequential.',\n defaultValue: 1, min: 1, max: 8, step: 1,\n },\n\n // \u2500\u2500 Logging \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n 'logging.minLevel': {\n type: 'select', category: 'logging', group: 'Output',\n label: 'Minimum log level',\n description: 'Only messages at this level or above are recorded.',\n defaultValue: 'info',\n options: [\n { value: 'trace', label: 'Trace' },\n { value: 'debug', label: 'Debug' },\n { value: 'info', label: 'Info' },\n { value: 'warn', label: 'Warning' },\n { value: 'error', label: 'Error' },\n { value: 'fatal', label: 'Fatal' },\n ],\n },\n 'logging.enableConsole': {\n type: 'boolean', category: 'logging', group: 'Output',\n label: 'Enable console output',\n description: 'Mirror log messages to the browser console.',\n defaultValue: false,\n },\n 'logging.historyLimit': {\n type: 'number', category: 'logging', group: 'History',\n label: 'Log history limit',\n description: 'Maximum number of log entries retained in memory.',\n defaultValue: 500, min: 50, max: 10000, step: 50,\n },\n\n // \u2500\u2500 Projects \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n 'projects.directory': {\n type: 'text', category: 'general', group: 'Projects',\n label: 'Projects directory',\n description: 'Default directory for new projects and bundled demos. Leave empty for OS default.',\n defaultValue: null,\n placeholder: 'OS default (%APPDATA%/EcoSim/projects)',\n },\n\n // \u2500\u2500 AI Assistant \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n // Provider, auth, model, and server settings are managed from the chat UI\n // modals. Only behavior settings appear here.\n 'ai.defaultMode': {\n type: 'select', category: 'ai', group: 'Behavior',\n label: 'Default mode',\n description: 'Default interaction mode for the AI assistant.',\n defaultValue: 'ask',\n options: [\n { value: 'ask', label: 'Ask (preview before applying)' },\n { value: 'edit', label: 'Edit (apply immediately)' },\n ],\n },\n 'ai.maxToolCalls': {\n type: 'number', category: 'ai', group: 'Behavior',\n label: 'Max tool calls',\n description: 'Maximum number of tool calls per AI turn.',\n defaultValue: 25, min: 1, max: 100, step: 1,\n },\n\n // \u2500\u2500 Notebook \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n 'notebook.cellWidthMode': {\n type: 'select', category: 'notebook', group: 'Layout',\n label: 'Cell width mode',\n description: 'Controls how wide all cells appear. \"Fixed\" constrains cells to a max-width; \"Full\" stretches all cells.',\n defaultValue: 'fixed',\n options: [\n { value: 'fixed', label: 'Fixed width (900px)' },\n { value: 'full', label: 'Full width' },\n ],\n },\n 'notebook.codeCellMaxHeight': {\n type: 'boolean', category: 'notebook', group: 'Layout',\n label: 'Limit code cell height',\n description: 'When enabled, code cells have a maximum height and scroll internally instead of expanding to show all content.',\n defaultValue: false,\n },\n 'notebook.paramCellMaxHeight': {\n type: 'boolean', category: 'notebook', group: 'Layout',\n label: 'Limit parameter cell height',\n description: 'When enabled, parameter cells have a maximum height and scroll internally instead of expanding to show all content.',\n defaultValue: false,\n },\n 'notebook.defaultCellView': {\n type: 'select', category: 'notebook', group: 'Layout',\n label: 'Default cell view',\n description: 'Which tab to show by default on all cells: Config (edit) or EcoLang (generated code).',\n defaultValue: 'config',\n options: [\n { value: 'config', label: 'Config' },\n { value: 'dsl', label: 'EcoLang' },\n ],\n },\n\n // \u2500\u2500 Advanced \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n 'editor.autosaveDelayMs': {\n type: 'number', category: 'advanced', group: 'Performance',\n label: 'Editor autosave delay (ms)',\n description: 'Delay before the function editor auto-saves changes.',\n defaultValue: 5000, min: 1000, max: 30000, step: 1000,\n },\n 'debug.logSettingsAccess': {\n type: 'boolean', category: 'advanced', group: 'Debug',\n label: 'Log settings access',\n description: 'Log all getSetting/setSetting calls to the console for debugging.',\n defaultValue: false,\n },\n});\n\n/**\n * Shell-owned categories. `order` is the sidebar sort key \u2014 it exists so a\n * registered slice can slot its category *between* two shell categories\n * instead of being stuck at the end.\n */\nconst CATEGORIES = Object.freeze([\n { id: 'general', label: 'General', icon: 'tune', description: 'Workspace behavior and notifications', order: 10 },\n { id: 'ai', label: 'AI Assistant', icon: 'smart_toy', description: 'AI assistant behavior', order: 30 },\n { id: 'window', label: 'Windows', icon: 'web_asset', description: 'Managed window animation and appearance', order: 40 },\n { id: 'data', label: 'Data', icon: 'database', description: 'Table display, import, and pipeline defaults', order: 50 },\n { id: 'logging', label: 'Logging', icon: 'terminal', description: 'Log level, console output, and history', order: 60 },\n { id: 'notebook', label: 'Notebook', icon: 'menu_book', description: 'Notebook editor layout and behavior', order: 70 },\n { id: 'advanced', label: 'Advanced', icon: 'code', description: 'Debug and developer settings', order: 80 },\n]);\n\nconst STORAGE_KEY = 'ecosim.settings.v1';\n\n// \u2500\u2500\u2500 Path / value helpers \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n/**\n * Traverse an object by dot-path parts and return the value.\n * @param {object} obj\n * @param {string[]} parts\n * @returns {*}\n */\nfunction _getNestedValue(obj, parts) {\n let current = obj;\n for (const part of parts) {\n if (current === null || current === undefined || typeof current !== 'object') {\n return undefined;\n }\n current = current[part];\n }\n return current;\n}\n\n/**\n * Set a value in a nested object by dot-path parts, creating intermediates.\n * @param {object} obj\n * @param {string[]} parts\n * @param {*} value\n */\nfunction _setNestedValue(obj, parts, value) {\n let current = obj;\n for (let i = 0; i < parts.length - 1; i++) {\n if (current[parts[i]] === undefined || current[parts[i]] === null || typeof current[parts[i]] !== 'object') {\n current[parts[i]] = {};\n }\n current = current[parts[i]];\n }\n current[parts[parts.length - 1]] = value;\n}\n\n/**\n * Deep equality check for primitives, arrays, and plain objects.\n */\nfunction _deepEqual(a, b) {\n if (a === b) return true;\n if (a == null || b == null) return a === b;\n if (typeof a !== typeof b) return false;\n if (Array.isArray(a)) {\n if (!Array.isArray(b) || a.length !== b.length) return false;\n return a.every((v, i) => _deepEqual(v, b[i]));\n }\n if (typeof a === 'object') {\n const keysA = Object.keys(a);\n const keysB = Object.keys(b);\n if (keysA.length !== keysB.length) return false;\n return keysA.every(k => _deepEqual(a[k], b[k]));\n }\n return false;\n}\n\n/** Deep clone via JSON \u2014 the value tree is JSON by construction (it persists). */\nfunction _clone(value) {\n return value === undefined ? undefined : JSON.parse(JSON.stringify(value));\n}\n\n/** Recursively merge `src` into `target` (plain objects only; arrays replace). */\nfunction _deepMerge(target, src) {\n for (const [key, value] of Object.entries(src || {})) {\n const isPlain = value !== null && typeof value === 'object' && !Array.isArray(value);\n if (isPlain) {\n if (target[key] === null || typeof target[key] !== 'object' || Array.isArray(target[key])) {\n target[key] = {};\n }\n _deepMerge(target[key], value);\n } else {\n target[key] = _clone(value);\n }\n }\n return target;\n}\n\n// \u2500\u2500\u2500 The store (the primitive) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n/**\n * Create an isolated, schema-driven settings store.\n *\n * The store owns no domain knowledge: everything it can hold, validate,\n * persist and render is described by the `defaults` / `schema` / `categories`\n * it is handed. Pass none and you get an empty store that an embedder fills\n * entirely via `extend()`.\n *\n * @param {object} [opts]\n * @param {object} [opts.defaults] Nested default values.\n * @param {Object<string, SettingDef>} [opts.schema] Dot-path -> definition.\n * @param {CategoryDef[]} [opts.categories]\n * @param {string} [opts.storageKey] localStorage key. Omit -> no persistence.\n * @param {object} [opts.eventBus] Optional; may be attached later.\n * @returns {object} Frozen store handle.\n */\nexport function createSettingsStore({\n defaults = {},\n schema = {},\n categories = [],\n storageKey = null,\n eventBus = null,\n} = {}) {\n const _defaults = _clone(defaults) ?? {};\n const _schema = { ...schema };\n let _categories = categories.map(c => ({ ...c }));\n const _values = _clone(defaults) ?? {};\n let _bus = eventBus;\n\n const _sortCategories = () => {\n // Array#sort is stable, so equal `order` keeps registration order.\n _categories.sort((a, b) => (a.order ?? 1000) - (b.order ?? 1000));\n };\n _sortCategories();\n\n const _debug = () => _values.debug?.logSettingsAccess;\n\n /**\n * Load user-persisted values from localStorage (sparse merge).\n * Only paths currently present in the schema are restored; anything else in\n * storage is left untouched in memory \u2014 that is what makes `extend()` able\n * to pick up a late-registered namespace's saved values.\n */\n const _load = () => {\n try {\n if (!storageKey || typeof localStorage === 'undefined') return;\n const raw = localStorage.getItem(storageKey);\n if (!raw) return;\n const saved = JSON.parse(raw);\n for (const path of Object.keys(_schema)) {\n const parts = path.split('.');\n const value = _getNestedValue(saved, parts);\n if (value !== undefined) {\n _setNestedValue(_values, parts, value);\n }\n }\n } catch (err) {\n console.warn('[Settings] Failed to load persisted settings', err);\n }\n };\n\n /** Persist values that differ from their defaults (sparse storage). */\n const _save = () => {\n try {\n if (!storageKey || typeof localStorage === 'undefined') return;\n const sparse = {};\n for (const path of Object.keys(_schema)) {\n const parts = path.split('.');\n const current = _getNestedValue(_values, parts);\n const def = _schema[path].defaultValue;\n if (!_deepEqual(current, def)) {\n _setNestedValue(sparse, parts, current);\n }\n }\n if (Object.keys(sparse).length === 0) {\n localStorage.removeItem(storageKey);\n } else {\n localStorage.setItem(storageKey, JSON.stringify(sparse));\n }\n } catch (err) {\n console.warn('[Settings] Failed to persist settings', err);\n }\n };\n\n const _emit = (path, value) => {\n if (!_bus) return;\n _bus.emit(`settings:${path}:changed`, { path, value });\n _bus.emit('settings:changed', { path, value });\n };\n\n _load();\n\n /**\n * Merge an additional slice in \u2014 an embedder's own namespace \u2014 then re-apply\n * persisted overrides for the newly-known paths.\n *\n * ORDERING RULE: call this before the first `set()`. `_save()` rebuilds\n * storage from `Object.keys(_schema)`, so a write that lands *before* a\n * namespace is registered would garbage-collect that namespace's saved\n * values. Registering at the top of bootstrap satisfies this trivially.\n *\n * @param {SettingsSlice} slice\n */\n const extend = ({ defaults: d = {}, schema: s = {}, categories: c = [] } = {}) => {\n _deepMerge(_defaults, d);\n _deepMerge(_values, d);\n Object.assign(_schema, s);\n for (const cat of c) {\n if (!cat?.id) continue;\n if (_categories.some(x => x.id === cat.id)) continue;\n _categories.push({ ...cat });\n }\n _sortCategories();\n _load(); // idempotent: overlays only paths it finds in storage\n };\n\n const get = (path, defaultValue = undefined) => {\n const parts = path.split('.');\n let current = _values;\n for (const part of parts) {\n if (current === null || current === undefined || typeof current !== 'object') {\n if (_debug()) console.warn(`[Settings] Path not found: ${path}`);\n return defaultValue;\n }\n current = current[part];\n }\n if (current === undefined) return defaultValue;\n if (_debug()) console.log(`[Settings] getSetting('${path}') =>`, current);\n return current;\n };\n\n const set = (path, value) => {\n const parts = path.split('.');\n const lastPart = parts.pop();\n let current = _values;\n for (const part of parts) {\n if (current[part] === undefined || current[part] === null) {\n current[part] = {};\n }\n if (typeof current[part] !== 'object') {\n console.warn(`[Settings] Cannot set '${path}': intermediate path is not an object`);\n return false;\n }\n current = current[part];\n }\n current[lastPart] = value;\n if (_debug()) console.log(`[Settings] setSetting('${path}', ${JSON.stringify(value)})`);\n _save();\n _emit(path, value);\n return true;\n };\n\n const getDefault = (path) => {\n const def = _schema[path];\n if (!def) return _clone(_getNestedValue(_defaults, path.split('.')));\n return _clone(def.defaultValue);\n };\n\n const resetOne = (path) => {\n const defaultVal = getDefault(path);\n if (defaultVal === undefined) {\n console.warn(`[Settings] No default found for '${path}'`);\n return false;\n }\n return set(path, defaultVal);\n };\n\n const resetCategoryFn = (categoryId) => {\n for (const [path, def] of Object.entries(_schema)) {\n if (def.category !== categoryId) continue;\n _setNestedValue(_values, path.split('.'), _clone(def.defaultValue));\n }\n _save();\n if (_bus) {\n _bus.emit('settings:category:reset', { category: categoryId });\n _bus.emit('settings:changed', { path: null, category: categoryId });\n }\n };\n\n const resetAll = () => {\n for (const [path, def] of Object.entries(_schema)) {\n _setNestedValue(_values, path.split('.'), _clone(def.defaultValue));\n }\n _save();\n if (_bus) {\n _bus.emit('settings:reset', {});\n _bus.emit('settings:changed', { path: null });\n }\n };\n\n const getByCategory = (categoryId) => {\n const result = [];\n for (const [path, def] of Object.entries(_schema)) {\n if (def.category === categoryId) result.push({ path, ...def });\n }\n return result;\n };\n\n return Object.freeze({\n get,\n set,\n extend,\n getDefault,\n resetOne,\n resetCategory: resetCategoryFn,\n resetAll,\n getAll: () => _clone(_values),\n getSchema: () => _schema,\n // Frozen snapshot: the old module-level CATEGORIES was a frozen const, and\n // a caller that sorted or spliced the live array in place would silently\n // reorder the Settings sidebar. Same objects, same order \u2014 just not ours\n // to wreck.\n getCategories: () => Object.freeze(_categories.slice()),\n getByCategory,\n setEventBus: (bus) => { _bus = bus; },\n });\n}\n\n// \u2500\u2500\u2500 The app-global binding over ONE store \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n//\n// A capability *port* (the host bridge) may have many implementations at once,\n// so a singleton there is a category error. A *value store* with a single\n// physical backing key has cardinality one by construction: two live stores in\n// one document would be two writers to `localStorage[STORAGE_KEY]`. The\n// singleton is the correct cardinality, not a shortcut \u2014 and the factory above\n// is the primitive, so tests and a standalone demo never touch this binding.\n\nconst _store = createSettingsStore({\n defaults: DEFAULTS,\n schema: SCHEMA,\n categories: CATEGORIES,\n storageKey: STORAGE_KEY,\n});\n\n/**\n * Push an embedder's settings namespace into the app-global store.\n *\n * This is the whole of \"push, don't pull\": the framework ships the shell's own\n * settings and the *embedder* hands over its own \u2014 the store never imports a\n * schema it does not own.\n *\n * MUST run before any setSetting() \u2014 see `extend()`.\n *\n * @param {SettingsSlice} slice\n */\nexport function registerSettings(slice) {\n _store.extend(slice);\n}\n\n/**\n * Register the EventBus instance for settings change notifications.\n * Called once during app bootstrap.\n * @param {object} eventBus\n */\nexport function registerSettingsEventBus(eventBus) {\n _store.setEventBus(eventBus);\n}\n\n/**\n * Get a setting value by dot-notation path.\n * @param {string} path - Dot-separated path (e.g., 'workspace.save.showToast')\n * @param {*} [defaultValue] - Fallback if path not found\n * @returns {*} The setting value or defaultValue\n */\nexport function getSetting(path, defaultValue = undefined) {\n return _store.get(path, defaultValue);\n}\n\n/**\n * Set a setting value. Persists to localStorage and emits change events.\n * @param {string} path - Dot-separated path\n * @param {*} value - Value to set\n * @returns {boolean} True if set successfully\n */\nexport function setSetting(path, value) {\n return _store.set(path, value);\n}\n\n/**\n * Get the default value for a setting path.\n * @param {string} path - Dot-separated path\n * @returns {*} The default value, or undefined if the path is unknown\n */\nexport function getDefaultValue(path) {\n return _store.getDefault(path);\n}\n\n/**\n * Reset a setting to its default value.\n * @param {string} path - Dot-separated path\n * @returns {boolean} True if reset successfully\n */\nexport function resetSetting(path) {\n return _store.resetOne(path);\n}\n\n/**\n * Reset all settings in a category to their defaults.\n * @param {string} categoryId\n */\nexport function resetCategory(categoryId) {\n _store.resetCategory(categoryId);\n}\n\n/** Reset all settings to their defaults. */\nexport function resetAllSettings() {\n _store.resetAll();\n}\n\n/**\n * Get all settings as a plain object (for serialization or debugging).\n * @returns {object} Deep copy of current settings\n */\nexport function getAllSettings() {\n return _store.getAll();\n}\n\n// \u2500\u2500\u2500 Schema / Category API (for Settings page UI) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n/**\n * Get the full settings schema (shell + every registered slice).\n * @returns {Object<string, SettingDef>}\n */\nexport function getSchema() {\n return _store.getSchema();\n}\n\n/**\n * Get the categories list, in sidebar order.\n * @returns {CategoryDef[]}\n */\nexport function getCategories() {\n return _store.getCategories();\n}\n\n/**\n * Get all settings definitions for a given category, in schema order.\n * @param {string} categoryId\n * @returns {Array<{path:string} & SettingDef>}\n */\nexport function getSettingsByCategory(categoryId) {\n return _store.getByCategory(categoryId);\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -0,0 +1,117 @@
1
+ import {
2
+ __esm,
3
+ __export
4
+ } from "./chunk-JYWURG5T.js";
5
+
6
+ // src/tiles/tile_registry.js
7
+ var tile_registry_exports = {};
8
+ __export(tile_registry_exports, {
9
+ TileRegistry: () => TileRegistry,
10
+ createWidget: () => createWidget,
11
+ default: () => tile_registry_default,
12
+ getAllWidgetTypes: () => getAllWidgetTypes,
13
+ getWidget: () => getWidget,
14
+ getWidgetCatalog: () => getWidgetCatalog,
15
+ getWidgetMetadata: () => getWidgetMetadata,
16
+ hasWidget: () => hasWidget,
17
+ registerWidget: () => registerWidget,
18
+ setTileLogger: () => setTileLogger
19
+ });
20
+ function setTileLogger(logger) {
21
+ _logger = logger;
22
+ }
23
+ function registerWidget(WidgetClass) {
24
+ if (!WidgetClass.TYPE) {
25
+ console.warn("[TileRegistry] Widget class missing TYPE property:", WidgetClass);
26
+ return;
27
+ }
28
+ registry.set(WidgetClass.TYPE, WidgetClass);
29
+ _logger?.info?.(`[TileRegistry] Registered widget: ${WidgetClass.TYPE}`);
30
+ }
31
+ function getWidget(type) {
32
+ return registry.get(type);
33
+ }
34
+ function hasWidget(type) {
35
+ return registry.has(type);
36
+ }
37
+ function getAllWidgetTypes() {
38
+ const types = [];
39
+ registry.forEach((WidgetClass, type) => {
40
+ types.push({
41
+ type,
42
+ title: WidgetClass.TITLE || type,
43
+ defaultSize: WidgetClass.DEFAULT_SIZE || { w: 4, h: 3 },
44
+ constraints: WidgetClass.SIZE_CONSTRAINTS || { minW: 2, minH: 2, maxW: 12, maxH: 8 }
45
+ });
46
+ });
47
+ return types;
48
+ }
49
+ function createWidget(type, options) {
50
+ const WidgetClass = registry.get(type);
51
+ if (!WidgetClass) {
52
+ console.warn(`[TileRegistry] Unknown widget type: ${type}`);
53
+ return null;
54
+ }
55
+ return new WidgetClass(options);
56
+ }
57
+ function getWidgetCatalog({ runType } = {}) {
58
+ const catalog = [];
59
+ registry.forEach((WidgetClass, type) => {
60
+ if (runType === "static" && WidgetClass.REQUIRES_MC) return;
61
+ catalog.push({
62
+ type,
63
+ title: WidgetClass.TITLE || type,
64
+ icon: WidgetClass.ICON || "widgets",
65
+ description: WidgetClass.DESCRIPTION || "",
66
+ defaultSize: WidgetClass.DEFAULT_SIZE || { w: 4, h: 3 },
67
+ requiresMC: !!WidgetClass.REQUIRES_MC
68
+ });
69
+ });
70
+ return catalog;
71
+ }
72
+ function getWidgetMetadata(type) {
73
+ const WidgetClass = registry.get(type);
74
+ if (!WidgetClass) return null;
75
+ return {
76
+ type,
77
+ title: WidgetClass.TITLE || type,
78
+ icon: WidgetClass.ICON || "widgets",
79
+ description: WidgetClass.DESCRIPTION || "",
80
+ defaultSize: WidgetClass.DEFAULT_SIZE || { w: 4, h: 3 },
81
+ requiresMC: !!WidgetClass.REQUIRES_MC
82
+ };
83
+ }
84
+ var registry, _logger, TileRegistry, tile_registry_default;
85
+ var init_tile_registry = __esm({
86
+ "src/tiles/tile_registry.js"() {
87
+ registry = /* @__PURE__ */ new Map();
88
+ _logger = null;
89
+ TileRegistry = {
90
+ register: registerWidget,
91
+ get: getWidget,
92
+ has: hasWidget,
93
+ getAll: getAllWidgetTypes,
94
+ create: createWidget,
95
+ getCatalog: getWidgetCatalog,
96
+ getMetadata: getWidgetMetadata,
97
+ _registry: registry
98
+ };
99
+ tile_registry_default = TileRegistry;
100
+ }
101
+ });
102
+
103
+ export {
104
+ setTileLogger,
105
+ registerWidget,
106
+ getWidget,
107
+ hasWidget,
108
+ getAllWidgetTypes,
109
+ createWidget,
110
+ getWidgetCatalog,
111
+ getWidgetMetadata,
112
+ TileRegistry,
113
+ tile_registry_default,
114
+ tile_registry_exports,
115
+ init_tile_registry
116
+ };
117
+ //# sourceMappingURL=chunk-FOOS3T5L.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/tiles/tile_registry.js"],
4
+ "sourcesContent": ["/**\n * tile_registry.js\n *\n * Registry for widget tile types.\n * Widgets register themselves here, and the grid uses this to instantiate tiles by type.\n */\n\n/** @type {Map<string, typeof import('./tile_base.js').TileBase>} */\nconst registry = new Map();\n\n/**\n * Register a widget class.\n * @param {typeof import('./tile_base.js').TileBase} WidgetClass - Widget class to register\n */\n// The registry used to trace registrations to `window.logger` \u2014 a global belonging\n// to one application. Give it a logger or it stays quiet.\nlet _logger = null;\n\n/** @param {{info?: Function, warn?: Function}|null} logger */\nexport function setTileLogger(logger) {\n _logger = logger;\n}\n\nexport function registerWidget(WidgetClass) {\n if (!WidgetClass.TYPE) {\n console.warn('[TileRegistry] Widget class missing TYPE property:', WidgetClass);\n return;\n }\n registry.set(WidgetClass.TYPE, WidgetClass);\n _logger?.info?.(`[TileRegistry] Registered widget: ${WidgetClass.TYPE}`);\n}\n\n/**\n * Get a widget class by type.\n * @param {string} type - Widget type identifier\n * @returns {typeof import('./tile_base.js').TileBase | undefined} Widget class or undefined\n */\nexport function getWidget(type) {\n return registry.get(type);\n}\n\n/**\n * Check if a widget type is registered.\n * @param {string} type - Widget type identifier\n * @returns {boolean}\n */\nexport function hasWidget(type) {\n return registry.has(type);\n}\n\n/**\n * Get all registered widget types.\n * @returns {Array<{type: string, title: string, defaultSize: {w: number, h: number}}>}\n */\nexport function getAllWidgetTypes() {\n const types = [];\n registry.forEach((WidgetClass, type) => {\n types.push({\n type,\n title: WidgetClass.TITLE || type,\n defaultSize: WidgetClass.DEFAULT_SIZE || { w: 4, h: 3 },\n constraints: WidgetClass.SIZE_CONSTRAINTS || { minW: 2, minH: 2, maxW: 12, maxH: 8 }\n });\n });\n return types;\n}\n\n/**\n * Create a widget instance.\n * @param {string} type - Widget type identifier\n * @param {Object} options - Constructor options for the widget\n * @returns {import('./tile_base.js').TileBase | null} Widget instance or null if type not found\n */\nexport function createWidget(type, options) {\n const WidgetClass = registry.get(type);\n if (!WidgetClass) {\n console.warn(`[TileRegistry] Unknown widget type: ${type}`);\n return null;\n }\n return new WidgetClass(options);\n}\n\n/**\n * Get widget metadata for the \"Add Widget\" menu.\n * @returns {Array<{type: string, title: string, icon: string, description: string}>}\n */\nexport function getWidgetCatalog({ runType } = {}) {\n const catalog = [];\n registry.forEach((WidgetClass, type) => {\n // Filter out MC-only widgets for static runs\n if (runType === 'static' && WidgetClass.REQUIRES_MC) return;\n\n catalog.push({\n type,\n title: WidgetClass.TITLE || type,\n icon: WidgetClass.ICON || 'widgets',\n description: WidgetClass.DESCRIPTION || '',\n defaultSize: WidgetClass.DEFAULT_SIZE || { w: 4, h: 3 },\n requiresMC: !!WidgetClass.REQUIRES_MC,\n });\n });\n return catalog;\n}\n\n/**\n * Get metadata for a specific widget type.\n * @param {string} type - Widget type identifier\n * @returns {Object|null} Widget metadata or null if not found\n */\nexport function getWidgetMetadata(type) {\n const WidgetClass = registry.get(type);\n if (!WidgetClass) return null;\n\n return {\n type,\n title: WidgetClass.TITLE || type,\n icon: WidgetClass.ICON || 'widgets',\n description: WidgetClass.DESCRIPTION || '',\n defaultSize: WidgetClass.DEFAULT_SIZE || { w: 4, h: 3 },\n requiresMC: !!WidgetClass.REQUIRES_MC,\n };\n}\n\n// Export registry for debugging\nexport const TileRegistry = {\n register: registerWidget,\n get: getWidget,\n has: hasWidget,\n getAll: getAllWidgetTypes,\n create: createWidget,\n getCatalog: getWidgetCatalog,\n getMetadata: getWidgetMetadata,\n _registry: registry\n};\n\nexport default TileRegistry;\n"],
5
+ "mappings": ";;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAmBO,SAAS,cAAc,QAAQ;AAClC,YAAU;AACd;AAEO,SAAS,eAAe,aAAa;AACxC,MAAI,CAAC,YAAY,MAAM;AACnB,YAAQ,KAAK,sDAAsD,WAAW;AAC9E;AAAA,EACJ;AACA,WAAS,IAAI,YAAY,MAAM,WAAW;AAC1C,WAAS,OAAO,qCAAqC,YAAY,IAAI,EAAE;AAC3E;AAOO,SAAS,UAAU,MAAM;AAC5B,SAAO,SAAS,IAAI,IAAI;AAC5B;AAOO,SAAS,UAAU,MAAM;AAC5B,SAAO,SAAS,IAAI,IAAI;AAC5B;AAMO,SAAS,oBAAoB;AAChC,QAAM,QAAQ,CAAC;AACf,WAAS,QAAQ,CAAC,aAAa,SAAS;AACpC,UAAM,KAAK;AAAA,MACP;AAAA,MACA,OAAO,YAAY,SAAS;AAAA,MAC5B,aAAa,YAAY,gBAAgB,EAAE,GAAG,GAAG,GAAG,EAAE;AAAA,MACtD,aAAa,YAAY,oBAAoB,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,IAAI,MAAM,EAAE;AAAA,IACvF,CAAC;AAAA,EACL,CAAC;AACD,SAAO;AACX;AAQO,SAAS,aAAa,MAAM,SAAS;AACxC,QAAM,cAAc,SAAS,IAAI,IAAI;AACrC,MAAI,CAAC,aAAa;AACd,YAAQ,KAAK,uCAAuC,IAAI,EAAE;AAC1D,WAAO;AAAA,EACX;AACA,SAAO,IAAI,YAAY,OAAO;AAClC;AAMO,SAAS,iBAAiB,EAAE,QAAQ,IAAI,CAAC,GAAG;AAC/C,QAAM,UAAU,CAAC;AACjB,WAAS,QAAQ,CAAC,aAAa,SAAS;AAEpC,QAAI,YAAY,YAAY,YAAY,YAAa;AAErD,YAAQ,KAAK;AAAA,MACT;AAAA,MACA,OAAO,YAAY,SAAS;AAAA,MAC5B,MAAM,YAAY,QAAQ;AAAA,MAC1B,aAAa,YAAY,eAAe;AAAA,MACxC,aAAa,YAAY,gBAAgB,EAAE,GAAG,GAAG,GAAG,EAAE;AAAA,MACtD,YAAY,CAAC,CAAC,YAAY;AAAA,IAC9B,CAAC;AAAA,EACL,CAAC;AACD,SAAO;AACX;AAOO,SAAS,kBAAkB,MAAM;AACpC,QAAM,cAAc,SAAS,IAAI,IAAI;AACrC,MAAI,CAAC,YAAa,QAAO;AAEzB,SAAO;AAAA,IACH;AAAA,IACA,OAAO,YAAY,SAAS;AAAA,IAC5B,MAAM,YAAY,QAAQ;AAAA,IAC1B,aAAa,YAAY,eAAe;AAAA,IACxC,aAAa,YAAY,gBAAgB,EAAE,GAAG,GAAG,GAAG,EAAE;AAAA,IACtD,YAAY,CAAC,CAAC,YAAY;AAAA,EAC9B;AACJ;AAzHA,IAQM,UAQF,SA4GS,cAWN;AAvIP;AAAA;AAQA,IAAM,WAAW,oBAAI,IAAI;AAQzB,IAAI,UAAU;AA4GP,IAAM,eAAe;AAAA,MACxB,UAAU;AAAA,MACV,KAAK;AAAA,MACL,KAAK;AAAA,MACL,QAAQ;AAAA,MACR,QAAQ;AAAA,MACR,YAAY;AAAA,MACZ,aAAa;AAAA,MACb,WAAW;AAAA,IACf;AAEA,IAAO,wBAAQ;AAAA;AAAA;",
6
+ "names": []
7
+ }
@@ -0,0 +1,32 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
3
+ var __getOwnPropNames = Object.getOwnPropertyNames;
4
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
5
+ var __esm = (fn, res, err) => function __init() {
6
+ if (err) throw err[0];
7
+ try {
8
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
9
+ } catch (e) {
10
+ throw err = [e], e;
11
+ }
12
+ };
13
+ var __export = (target, all) => {
14
+ for (var name in all)
15
+ __defProp(target, name, { get: all[name], enumerable: true });
16
+ };
17
+ var __copyProps = (to, from, except, desc) => {
18
+ if (from && typeof from === "object" || typeof from === "function") {
19
+ for (let key of __getOwnPropNames(from))
20
+ if (!__hasOwnProp.call(to, key) && key !== except)
21
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
22
+ }
23
+ return to;
24
+ };
25
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
26
+
27
+ export {
28
+ __esm,
29
+ __export,
30
+ __toCommonJS
31
+ };
32
+ //# sourceMappingURL=chunk-JYWURG5T.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": [],
4
+ "sourcesContent": [],
5
+ "mappings": "",
6
+ "names": []
7
+ }
@@ -0,0 +1,276 @@
1
+ // src/ui/components/action_dropdown.js
2
+ var EDGE_MARGIN = 8;
3
+ var GAP = 8;
4
+ var ActionDropdown = class _ActionDropdown {
5
+ /**
6
+ * @param {Object} config
7
+ * @param {HTMLElement} config.trigger - Button element that triggers the dropdown
8
+ * @param {Array} config.options - Array of option objects with { type, label, icon, description? }
9
+ * @param {Function} config.onSelect - Callback when an option is selected
10
+ * @param {string} [config.className=''] - Additional CSS class for the dropdown
11
+ * @param {string} [config.menuId] - Optional ID for the menu element
12
+ */
13
+ constructor(config) {
14
+ this.trigger = config.trigger;
15
+ this.options = config.options || [];
16
+ this.onSelect = config.onSelect;
17
+ this.className = config.className || "";
18
+ this.menuId = config.menuId;
19
+ this.isOpen = false;
20
+ this.menuEl = null;
21
+ this._boundHandleDocumentClick = this._handleDocumentClick.bind(this);
22
+ this._boundHandleKeydown = this._handleKeydown.bind(this);
23
+ this._boundHandleTriggerClick = this._handleTriggerClick.bind(this);
24
+ this._init();
25
+ }
26
+ /**
27
+ * Initialize the dropdown.
28
+ * @private
29
+ */
30
+ _init() {
31
+ if (!this.trigger) {
32
+ console.warn("[ActionDropdown] No trigger element provided");
33
+ return;
34
+ }
35
+ this._createMenu();
36
+ this.trigger.addEventListener("click", this._boundHandleTriggerClick);
37
+ }
38
+ /**
39
+ * Create the dropdown menu element.
40
+ * @private
41
+ */
42
+ _createMenu() {
43
+ const menu = document.createElement("div");
44
+ menu.className = `twm-action-dropdown-menu ${this.className}`.trim();
45
+ menu.setAttribute("role", "menu");
46
+ menu.hidden = true;
47
+ if (this.menuId) {
48
+ menu.id = this.menuId;
49
+ }
50
+ this._renderOptions(menu);
51
+ document.body.appendChild(menu);
52
+ this.menuEl = menu;
53
+ }
54
+ /**
55
+ * Render option buttons into a container element.
56
+ * @param {HTMLElement} container
57
+ * @private
58
+ */
59
+ _renderOptions(container) {
60
+ container.innerHTML = "";
61
+ this.options.forEach((opt, index) => {
62
+ const optionBtn = document.createElement("button");
63
+ optionBtn.type = "button";
64
+ optionBtn.className = "twm-action-dropdown-option";
65
+ optionBtn.dataset.index = index;
66
+ optionBtn.dataset.type = opt.type;
67
+ optionBtn.setAttribute("role", "menuitem");
68
+ const iconHtml = opt.icon ? `<span class="material-symbols-outlined twm-option-icon">${opt.icon}</span>` : "";
69
+ const descHtml = opt.description ? `<span class="twm-option-desc">${opt.description}</span>` : "";
70
+ optionBtn.innerHTML = `
71
+ ${iconHtml}
72
+ <span class="option-text">
73
+ <span class="twm-option-label">${opt.label}</span>
74
+ ${descHtml}
75
+ </span>
76
+ `;
77
+ optionBtn.addEventListener("click", (e) => {
78
+ e.preventDefault();
79
+ e.stopPropagation();
80
+ this._selectOption(opt);
81
+ });
82
+ container.appendChild(optionBtn);
83
+ });
84
+ }
85
+ /**
86
+ * Handle trigger button click.
87
+ * @param {MouseEvent} e
88
+ * @private
89
+ */
90
+ _handleTriggerClick(e) {
91
+ e.preventDefault();
92
+ e.stopPropagation();
93
+ this.toggle();
94
+ }
95
+ /**
96
+ * Toggle the dropdown open/closed.
97
+ * @param {boolean} [forceState] - Optional forced state
98
+ */
99
+ toggle(forceState) {
100
+ const shouldOpen = typeof forceState === "boolean" ? forceState : !this.isOpen;
101
+ if (shouldOpen) {
102
+ this.open();
103
+ } else {
104
+ this.close();
105
+ }
106
+ }
107
+ /**
108
+ * Open the dropdown with auto-positioning.
109
+ */
110
+ open() {
111
+ if (this.isOpen || !this.menuEl) return;
112
+ this.isOpen = true;
113
+ this.menuEl.hidden = false;
114
+ _ActionDropdown.position(this.trigger, this.menuEl);
115
+ requestAnimationFrame(() => {
116
+ this.menuEl.classList.add("visible");
117
+ });
118
+ this.trigger?.classList.add("twm-is-open");
119
+ document.addEventListener("click", this._boundHandleDocumentClick, true);
120
+ document.addEventListener("keydown", this._boundHandleKeydown);
121
+ const firstOption = this.menuEl.querySelector(".twm-action-dropdown-option");
122
+ firstOption?.focus();
123
+ }
124
+ /**
125
+ * Close the dropdown.
126
+ */
127
+ close() {
128
+ if (!this.isOpen) return;
129
+ this.isOpen = false;
130
+ if (this.menuEl) {
131
+ this.menuEl.classList.remove("visible");
132
+ this.menuEl.hidden = true;
133
+ }
134
+ this.trigger?.classList.remove("twm-is-open");
135
+ document.removeEventListener("click", this._boundHandleDocumentClick, true);
136
+ document.removeEventListener("keydown", this._boundHandleKeydown);
137
+ }
138
+ /**
139
+ * Handle document click (close on outside click).
140
+ * @param {MouseEvent} e
141
+ * @private
142
+ */
143
+ _handleDocumentClick(e) {
144
+ if (!this.isOpen) return;
145
+ if (this.menuEl?.contains(e.target) || this.trigger?.contains(e.target)) {
146
+ return;
147
+ }
148
+ this.close();
149
+ }
150
+ /**
151
+ * Handle keyboard events.
152
+ * @param {KeyboardEvent} e
153
+ * @private
154
+ */
155
+ _handleKeydown(e) {
156
+ if (e.key === "Escape") {
157
+ this.close();
158
+ this.trigger?.focus();
159
+ return;
160
+ }
161
+ if (e.key === "ArrowDown" || e.key === "ArrowUp") {
162
+ e.preventDefault();
163
+ this._navigateOptions(e.key === "ArrowDown" ? 1 : -1);
164
+ }
165
+ }
166
+ /**
167
+ * Navigate options with arrow keys.
168
+ * @param {number} direction - 1 for down, -1 for up
169
+ * @private
170
+ */
171
+ _navigateOptions(direction) {
172
+ const options = this.menuEl?.querySelectorAll(".twm-action-dropdown-option");
173
+ if (!options || options.length === 0) return;
174
+ const currentIndex = Array.from(options).findIndex((opt) => opt === document.activeElement);
175
+ let nextIndex = currentIndex + direction;
176
+ if (nextIndex < 0) nextIndex = options.length - 1;
177
+ if (nextIndex >= options.length) nextIndex = 0;
178
+ options[nextIndex]?.focus();
179
+ }
180
+ /**
181
+ * Handle option selection.
182
+ * @param {Object} option - The selected option
183
+ * @private
184
+ */
185
+ _selectOption(option) {
186
+ this.close();
187
+ if (this.onSelect) {
188
+ this.onSelect(option);
189
+ }
190
+ }
191
+ /**
192
+ * Update the options dynamically.
193
+ * @param {Array} newOptions - New options array
194
+ */
195
+ setOptions(newOptions) {
196
+ this.options = newOptions;
197
+ if (this.menuEl) {
198
+ this._renderOptions(this.menuEl);
199
+ }
200
+ }
201
+ /**
202
+ * Destroy the dropdown and clean up.
203
+ */
204
+ destroy() {
205
+ this.close();
206
+ this.trigger?.removeEventListener("click", this._boundHandleTriggerClick);
207
+ this.menuEl?.remove();
208
+ this.menuEl = null;
209
+ this.trigger = null;
210
+ this.options = [];
211
+ this.onSelect = null;
212
+ }
213
+ // =========================================================================
214
+ // STATIC — shared auto-positioning for any trigger + menu pair
215
+ // =========================================================================
216
+ /**
217
+ * Position a dropdown menu relative to a trigger element.
218
+ * Uses `position: fixed` to avoid overflow clipping.
219
+ * Picks above/below based on available space, aligns left/right edge,
220
+ * and applies `max-height` + `overflow-y: auto` when the menu is taller
221
+ * than the available space.
222
+ *
223
+ * @param {HTMLElement} trigger - The element the dropdown opens from
224
+ * @param {HTMLElement} menu - The dropdown menu element to position
225
+ */
226
+ static position(trigger, menu) {
227
+ if (!trigger || !menu) return;
228
+ Object.assign(menu.style, {
229
+ position: "fixed",
230
+ top: "auto",
231
+ bottom: "auto",
232
+ left: "auto",
233
+ right: "auto",
234
+ maxHeight: "",
235
+ overflowY: ""
236
+ });
237
+ const triggerRect = trigger.getBoundingClientRect();
238
+ const menuRect = menu.getBoundingClientRect();
239
+ const vw = window.innerWidth;
240
+ const vh = window.innerHeight;
241
+ const spaceBelow = vh - triggerRect.bottom - GAP - EDGE_MARGIN;
242
+ const spaceAbove = triggerRect.top - GAP - EDGE_MARGIN;
243
+ let top;
244
+ let maxHeight;
245
+ if (menuRect.height <= spaceBelow) {
246
+ top = triggerRect.bottom + GAP;
247
+ maxHeight = spaceBelow;
248
+ } else if (menuRect.height <= spaceAbove) {
249
+ top = triggerRect.top - GAP - menuRect.height;
250
+ maxHeight = spaceAbove;
251
+ } else if (spaceBelow >= spaceAbove) {
252
+ top = triggerRect.bottom + GAP;
253
+ maxHeight = spaceBelow;
254
+ } else {
255
+ maxHeight = spaceAbove;
256
+ top = EDGE_MARGIN;
257
+ }
258
+ let left = triggerRect.left;
259
+ if (left + menuRect.width > vw - EDGE_MARGIN) {
260
+ left = triggerRect.right - menuRect.width;
261
+ }
262
+ left = Math.max(EDGE_MARGIN, Math.min(left, vw - menuRect.width - EDGE_MARGIN));
263
+ Object.assign(menu.style, {
264
+ position: "fixed",
265
+ top: `${Math.round(top)}px`,
266
+ left: `${Math.round(left)}px`,
267
+ maxHeight: `${Math.round(maxHeight)}px`,
268
+ overflowY: "auto"
269
+ });
270
+ }
271
+ };
272
+
273
+ export {
274
+ ActionDropdown
275
+ };
276
+ //# sourceMappingURL=chunk-TLZUUFOE.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/ui/components/action_dropdown.js"],
4
+ "sourcesContent": ["/**\n * action_dropdown.js\n *\n * Reusable dropdown component for action buttons (Add Scenario, Add Widget, etc).\n * Handles opening, closing, auto-positioning, keyboard navigation, and outside clicks.\n *\n * Auto-positioning: measures available viewport space around the trigger element\n * and picks the best direction (above/below) and alignment (left/right).\n * Adds scrolling when the menu would exceed available vertical space.\n */\n\n/** Viewport margin in px \u2014 dropdown stays this far from edges. */\nconst EDGE_MARGIN = 8;\n/** Gap between trigger and dropdown in px. */\nconst GAP = 8;\n\n/**\n * ActionDropdown - A reusable dropdown component with smart auto-positioning.\n *\n * Usage:\n * ```javascript\n * const dropdown = new ActionDropdown({\n * trigger: buttonElement,\n * options: [\n * { type: 'option1', label: 'Option 1', icon: 'icon_name', description: 'Description' },\n * { type: 'option2', label: 'Option 2', icon: 'icon_name', description: 'Description' }\n * ],\n * onSelect: (option) => console.log('Selected:', option.type),\n * });\n *\n * // Control programmatically\n * dropdown.open();\n * dropdown.close();\n * dropdown.toggle();\n * dropdown.destroy();\n * ```\n */\nexport class ActionDropdown {\n /**\n * @param {Object} config\n * @param {HTMLElement} config.trigger - Button element that triggers the dropdown\n * @param {Array} config.options - Array of option objects with { type, label, icon, description? }\n * @param {Function} config.onSelect - Callback when an option is selected\n * @param {string} [config.className=''] - Additional CSS class for the dropdown\n * @param {string} [config.menuId] - Optional ID for the menu element\n */\n constructor(config) {\n this.trigger = config.trigger;\n this.options = config.options || [];\n this.onSelect = config.onSelect;\n this.className = config.className || '';\n this.menuId = config.menuId;\n\n this.isOpen = false;\n this.menuEl = null;\n\n this._boundHandleDocumentClick = this._handleDocumentClick.bind(this);\n this._boundHandleKeydown = this._handleKeydown.bind(this);\n this._boundHandleTriggerClick = this._handleTriggerClick.bind(this);\n\n this._init();\n }\n\n /**\n * Initialize the dropdown.\n * @private\n */\n _init() {\n if (!this.trigger) {\n console.warn('[ActionDropdown] No trigger element provided');\n return;\n }\n\n this._createMenu();\n this.trigger.addEventListener('click', this._boundHandleTriggerClick);\n }\n\n /**\n * Create the dropdown menu element.\n * @private\n */\n _createMenu() {\n const menu = document.createElement('div');\n menu.className = `twm-action-dropdown-menu ${this.className}`.trim();\n menu.setAttribute('role', 'menu');\n menu.hidden = true;\n\n if (this.menuId) {\n menu.id = this.menuId;\n }\n\n this._renderOptions(menu);\n\n // Append to body so position:fixed works without clipping.\n document.body.appendChild(menu);\n\n this.menuEl = menu;\n }\n\n /**\n * Render option buttons into a container element.\n * @param {HTMLElement} container\n * @private\n */\n _renderOptions(container) {\n container.innerHTML = '';\n this.options.forEach((opt, index) => {\n const optionBtn = document.createElement('button');\n optionBtn.type = 'button';\n optionBtn.className = 'twm-action-dropdown-option';\n optionBtn.dataset.index = index;\n optionBtn.dataset.type = opt.type;\n optionBtn.setAttribute('role', 'menuitem');\n\n const iconHtml = opt.icon\n ? `<span class=\"material-symbols-outlined twm-option-icon\">${opt.icon}</span>`\n : '';\n\n const descHtml = opt.description\n ? `<span class=\"twm-option-desc\">${opt.description}</span>`\n : '';\n\n optionBtn.innerHTML = `\n ${iconHtml}\n <span class=\"option-text\">\n <span class=\"twm-option-label\">${opt.label}</span>\n ${descHtml}\n </span>\n `;\n\n optionBtn.addEventListener('click', (e) => {\n e.preventDefault();\n e.stopPropagation();\n this._selectOption(opt);\n });\n\n container.appendChild(optionBtn);\n });\n }\n\n /**\n * Handle trigger button click.\n * @param {MouseEvent} e\n * @private\n */\n _handleTriggerClick(e) {\n e.preventDefault();\n e.stopPropagation();\n this.toggle();\n }\n\n /**\n * Toggle the dropdown open/closed.\n * @param {boolean} [forceState] - Optional forced state\n */\n toggle(forceState) {\n const shouldOpen = typeof forceState === 'boolean' ? forceState : !this.isOpen;\n if (shouldOpen) {\n this.open();\n } else {\n this.close();\n }\n }\n\n /**\n * Open the dropdown with auto-positioning.\n */\n open() {\n if (this.isOpen || !this.menuEl) return;\n\n this.isOpen = true;\n this.menuEl.hidden = false;\n\n // Position against trigger\n ActionDropdown.position(this.trigger, this.menuEl);\n\n // Animate in\n requestAnimationFrame(() => {\n this.menuEl.classList.add('visible');\n });\n\n // Update trigger state\n this.trigger?.classList.add('twm-is-open');\n\n // Add document listeners\n document.addEventListener('click', this._boundHandleDocumentClick, true);\n document.addEventListener('keydown', this._boundHandleKeydown);\n\n // Focus first option for keyboard accessibility\n const firstOption = this.menuEl.querySelector('.twm-action-dropdown-option');\n firstOption?.focus();\n }\n\n /**\n * Close the dropdown.\n */\n close() {\n if (!this.isOpen) return;\n\n this.isOpen = false;\n\n if (this.menuEl) {\n this.menuEl.classList.remove('visible');\n this.menuEl.hidden = true;\n }\n\n // Update trigger state\n this.trigger?.classList.remove('twm-is-open');\n\n // Remove document listeners\n document.removeEventListener('click', this._boundHandleDocumentClick, true);\n document.removeEventListener('keydown', this._boundHandleKeydown);\n }\n\n /**\n * Handle document click (close on outside click).\n * @param {MouseEvent} e\n * @private\n */\n _handleDocumentClick(e) {\n if (!this.isOpen) return;\n\n // Don't close if clicking inside dropdown or trigger\n if (this.menuEl?.contains(e.target) || this.trigger?.contains(e.target)) {\n return;\n }\n\n this.close();\n }\n\n /**\n * Handle keyboard events.\n * @param {KeyboardEvent} e\n * @private\n */\n _handleKeydown(e) {\n if (e.key === 'Escape') {\n this.close();\n this.trigger?.focus();\n return;\n }\n\n // Arrow key navigation\n if (e.key === 'ArrowDown' || e.key === 'ArrowUp') {\n e.preventDefault();\n this._navigateOptions(e.key === 'ArrowDown' ? 1 : -1);\n }\n }\n\n /**\n * Navigate options with arrow keys.\n * @param {number} direction - 1 for down, -1 for up\n * @private\n */\n _navigateOptions(direction) {\n const options = this.menuEl?.querySelectorAll('.twm-action-dropdown-option');\n if (!options || options.length === 0) return;\n\n const currentIndex = Array.from(options).findIndex(opt => opt === document.activeElement);\n let nextIndex = currentIndex + direction;\n\n if (nextIndex < 0) nextIndex = options.length - 1;\n if (nextIndex >= options.length) nextIndex = 0;\n\n options[nextIndex]?.focus();\n }\n\n /**\n * Handle option selection.\n * @param {Object} option - The selected option\n * @private\n */\n _selectOption(option) {\n this.close();\n\n if (this.onSelect) {\n this.onSelect(option);\n }\n }\n\n /**\n * Update the options dynamically.\n * @param {Array} newOptions - New options array\n */\n setOptions(newOptions) {\n this.options = newOptions;\n if (this.menuEl) {\n this._renderOptions(this.menuEl);\n }\n }\n\n /**\n * Destroy the dropdown and clean up.\n */\n destroy() {\n this.close();\n\n // Remove trigger listener\n this.trigger?.removeEventListener('click', this._boundHandleTriggerClick);\n\n // Remove menu element\n this.menuEl?.remove();\n this.menuEl = null;\n\n this.trigger = null;\n this.options = [];\n this.onSelect = null;\n }\n\n // =========================================================================\n // STATIC \u2014 shared auto-positioning for any trigger + menu pair\n // =========================================================================\n\n /**\n * Position a dropdown menu relative to a trigger element.\n * Uses `position: fixed` to avoid overflow clipping.\n * Picks above/below based on available space, aligns left/right edge,\n * and applies `max-height` + `overflow-y: auto` when the menu is taller\n * than the available space.\n *\n * @param {HTMLElement} trigger - The element the dropdown opens from\n * @param {HTMLElement} menu - The dropdown menu element to position\n */\n static position(trigger, menu) {\n if (!trigger || !menu) return;\n\n // Reset any prior inline position so natural size can be measured.\n Object.assign(menu.style, {\n position: 'fixed',\n top: 'auto',\n bottom: 'auto',\n left: 'auto',\n right: 'auto',\n maxHeight: '',\n overflowY: ''\n });\n\n const triggerRect = trigger.getBoundingClientRect();\n const menuRect = menu.getBoundingClientRect();\n const vw = window.innerWidth;\n const vh = window.innerHeight;\n\n // \u2500\u2500 Vertical: prefer below, flip above if insufficient room \u2500\u2500\u2500\u2500\u2500\u2500\u2500\n const spaceBelow = vh - triggerRect.bottom - GAP - EDGE_MARGIN;\n const spaceAbove = triggerRect.top - GAP - EDGE_MARGIN;\n\n let top;\n let maxHeight;\n\n if (menuRect.height <= spaceBelow) {\n // Fits below \u2014 use natural height.\n top = triggerRect.bottom + GAP;\n maxHeight = spaceBelow;\n } else if (menuRect.height <= spaceAbove) {\n // Fits above \u2014 use natural height.\n top = triggerRect.top - GAP - menuRect.height;\n maxHeight = spaceAbove;\n } else if (spaceBelow >= spaceAbove) {\n // More space below \u2014 scroll.\n top = triggerRect.bottom + GAP;\n maxHeight = spaceBelow;\n } else {\n // More space above \u2014 scroll.\n maxHeight = spaceAbove;\n top = EDGE_MARGIN;\n }\n\n // \u2500\u2500 Horizontal: align left-edge with trigger, flip if off-screen \u2500\u2500\n let left = triggerRect.left;\n\n if (left + menuRect.width > vw - EDGE_MARGIN) {\n // Align right edges instead.\n left = triggerRect.right - menuRect.width;\n }\n\n // Still off-screen? Clamp to edges.\n left = Math.max(EDGE_MARGIN, Math.min(left, vw - menuRect.width - EDGE_MARGIN));\n\n Object.assign(menu.style, {\n position: 'fixed',\n top: `${Math.round(top)}px`,\n left: `${Math.round(left)}px`,\n maxHeight: `${Math.round(maxHeight)}px`,\n overflowY: 'auto'\n });\n }\n}\n\nexport default ActionDropdown;\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }