@eclipse-docks/core 0.7.107 → 0.7.109

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 (41) hide show
  1. package/dist/api/index.js +4 -4
  2. package/dist/api/services.d.ts +1 -1
  3. package/dist/api/services.d.ts.map +1 -1
  4. package/dist/api/types.d.ts +1 -1
  5. package/dist/api/types.d.ts.map +1 -1
  6. package/dist/{api-gIOIP8P6.js → api-CuHseuRW.js} +4 -4
  7. package/dist/{api-gIOIP8P6.js.map → api-CuHseuRW.js.map} +1 -1
  8. package/dist/components/command.d.ts.map +1 -1
  9. package/dist/core/commandregistry.d.ts +2 -2
  10. package/dist/core/commandregistry.d.ts.map +1 -1
  11. package/dist/core/contributionregistry.d.ts +10 -2
  12. package/dist/core/contributionregistry.d.ts.map +1 -1
  13. package/dist/core/dialogservice.d.ts.map +1 -1
  14. package/dist/dialogs/form-dialog.d.ts +46 -0
  15. package/dist/dialogs/form-dialog.d.ts.map +1 -0
  16. package/dist/dialogs/index.d.ts +1 -0
  17. package/dist/dialogs/index.d.ts.map +1 -1
  18. package/dist/index.js +4 -4
  19. package/dist/parts/contextmenu.d.ts.map +1 -1
  20. package/dist/parts/index.js +1 -1
  21. package/dist/parts/toolbar.d.ts.map +1 -1
  22. package/dist/{parts-Cl4eHsto.js → parts-D__47G7C.js} +171 -8
  23. package/dist/parts-D__47G7C.js.map +1 -0
  24. package/dist/{widget-BgBerzhp.js → widget-DbolQH0C.js} +19 -6
  25. package/dist/widget-DbolQH0C.js.map +1 -0
  26. package/dist/widgets/index.js +1 -1
  27. package/dist/{widgets-Cgi8WuOZ.js → widgets-SScpQQr9.js} +2 -2
  28. package/dist/{widgets-Cgi8WuOZ.js.map → widgets-SScpQQr9.js.map} +1 -1
  29. package/package.json +1 -1
  30. package/src/api/services.ts +7 -1
  31. package/src/api/types.ts +4 -1
  32. package/src/components/command.ts +14 -3
  33. package/src/core/commandregistry.ts +1 -1
  34. package/src/core/contributionregistry.ts +33 -8
  35. package/src/core/dialogservice.ts +6 -1
  36. package/src/dialogs/form-dialog.ts +239 -0
  37. package/src/dialogs/index.ts +8 -0
  38. package/src/parts/contextmenu.ts +6 -2
  39. package/src/parts/toolbar.ts +5 -3
  40. package/dist/parts-Cl4eHsto.js.map +0 -1
  41. package/dist/widget-BgBerzhp.js.map +0 -1
@@ -141,16 +141,29 @@ function listContributionNameRemaps() {
141
141
  //#endregion
142
142
  //#region src/core/contributionregistry.ts
143
143
  var TOPIC_CONTRIBUTEIONS_CHANGED = "events/contributionregistry/contributionsChanged";
144
+ /** Default true when `visible` is omitted. */
145
+ function getContributionVisible(contribution) {
146
+ const visible = contribution.visible;
147
+ if (!visible) return true;
148
+ return visible.get() !== false;
149
+ }
150
+ /** Default false when `disabled` is omitted. */
151
+ function getContributionDisabled(contribution) {
152
+ const disabled = contribution.disabled;
153
+ if (!disabled) return false;
154
+ return disabled.get() === true;
155
+ }
156
+ function wrapContributionReactiveFlags(contribution) {
157
+ if (contribution.disabled instanceof Function) contribution.disabled = new Signal.Computed(contribution.disabled);
158
+ if (contribution.visible instanceof Function) contribution.visible = new Signal.Computed(contribution.visible);
159
+ }
144
160
  var ContributionRegistry = class {
145
161
  constructor() {
146
162
  this.contributions = /* @__PURE__ */ new Map();
147
163
  }
148
164
  registerContribution(target, contribution) {
149
165
  const targetSlot = this.getOrCreateSlot(target);
150
- if ("command" in contribution) {
151
- const cmd = contribution;
152
- if (cmd.disabled instanceof Function) cmd.disabled = new Signal.Computed(cmd.disabled);
153
- }
166
+ wrapContributionReactiveFlags(contribution);
154
167
  targetSlot.push(contribution);
155
168
  publish(TOPIC_CONTRIBUTEIONS_CHANGED, {
156
169
  target,
@@ -437,6 +450,6 @@ var DocksWidget = class extends DocksWidgetBase {
437
450
  }
438
451
  };
439
452
  //#endregion
440
- export { toastWarning as C, toastInfo as S, applyConfigNameRemaps as _, TOPIC_COMMAND_REGISTERED as a, overrideContributionTargets as b, EMPTY_SIGNALPORT as c, activeSelectionSignal as d, activeTasksSignal as f, contributionRegistry as g, TOPIC_CONTRIBUTEIONS_CHANGED as h, CommandStack as i, activeEditorSignal as l, perspectiveSwitchedSignal as m, watchSignal as n, commandRegistry as o, partDirtySignal as p, CommandRegistry as r, registerAll as s, DocksWidget as t, activePartSignal as u, contributionTargetMappingRegistry as v, toastError as x, listContributionNameRemaps as y };
453
+ export { toastError as C, overrideContributionTargets as S, toastWarning as T, getContributionDisabled as _, TOPIC_COMMAND_REGISTERED as a, contributionTargetMappingRegistry as b, EMPTY_SIGNALPORT as c, activeSelectionSignal as d, activeTasksSignal as f, contributionRegistry as g, TOPIC_CONTRIBUTEIONS_CHANGED as h, CommandStack as i, activeEditorSignal as l, perspectiveSwitchedSignal as m, watchSignal as n, commandRegistry as o, partDirtySignal as p, CommandRegistry as r, registerAll as s, DocksWidget as t, activePartSignal as u, getContributionVisible as v, toastInfo as w, listContributionNameRemaps as x, applyConfigNameRemaps as y };
441
454
 
442
- //# sourceMappingURL=widget-BgBerzhp.js.map
455
+ //# sourceMappingURL=widget-DbolQH0C.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"widget-DbolQH0C.js","names":[],"sources":["../src/core/toast.ts","../src/core/contribution-mapping.ts","../src/core/contributionregistry.ts","../src/core/appstate.ts","../src/core/commandregistry.ts","../src/externals/wa-tree-shadow-parts.ts","../src/core/signals.ts","../src/widgets/widget.ts"],"sourcesContent":["import Toastify from 'toastify-js';\nimport 'toastify-js/src/toastify.css';\nimport { createLogger } from './logger';\n\nconst logger = createLogger('Toast');\n\nconst DURATION = 4000;\nconst BASE = { duration: DURATION, gravity: 'bottom' as const, position: 'right' as const, close: true };\n\nconst show = (msg: string, style: { background: string; color: string }) => {\n Toastify({ ...BASE, text: msg, style }).showToast();\n};\n\nexport const toastInfo = (msg: string) => {\n logger.info(msg);\n show(msg, { background: 'var(--wa-color-brand-50)', color: 'var(--wa-color-brand-on)' });\n};\n\nexport const toastError = (msg: string) => {\n logger.error(msg);\n show(msg, { background: 'var(--wa-color-danger-50)', color: 'var(--wa-color-danger-on)' });\n};\n\nexport const toastWarning = (msg: string) => {\n logger.warn(msg);\n show(msg, { background: 'var(--wa-color-warning-50)', color: 'var(--wa-color-warning-on)' });\n};\n","import { rootContext } from \"./di\";\nimport type { Contribution } from \"./contributionregistry\";\n\nexport interface ContributionNameRemap {\n /**\n * Globally unique, namespaced contribution name\n * (e.g. \"view.filebrowser\", \"toolbar.filebrowser.rename\").\n */\n name: string;\n /**\n * Effective target slots for this contribution.\n * If empty, the original target is used.\n */\n targets: string[];\n}\n\ninterface NameRemapState {\n appTargets: string[] | undefined;\n globalTargets: string[] | undefined;\n}\n\n/**\n * Registry responsible for resolving effective contribution targets.\n *\n * It supports:\n * - Global (framework-level) remaps.\n * - App-level remaps coming from AppDefinition.\n * - Multiple targets per contribution.\n *\n * Resolution precedence:\n * app-level remap > global remap > original target\n */\nclass ContributionTargetMappingRegistry {\n private globalNameRemaps = new Map<string, string[]>();\n private appNameRemaps = new Map<string, string[]>();\n\n setGlobalNameRemap(name: string, targets: string[]): void {\n this.globalNameRemaps.set(name, this.normalizeTargets(targets));\n }\n\n /** Clears all remaps. For testing only. */\n resetForTesting(): void {\n this.globalNameRemaps.clear();\n this.appNameRemaps.clear();\n }\n\n applyAppNameRemaps(remaps: ContributionNameRemap[] | undefined | null): void {\n this.appNameRemaps.clear();\n if (!remaps) return;\n for (const remap of remaps) {\n this.appNameRemaps.set(remap.name, this.normalizeTargets(remap.targets));\n }\n }\n\n /**\n * Returns the effective target slots for a contribution given its original target.\n *\n * Semantics:\n * - When a remap exists with a non-empty targets array, those targets are used.\n * - When a remap exists with an empty targets array, the contribution is disabled (no targets).\n * - When no remap exists, the original target is used.\n */\n getEffectiveTargets(originalTarget: string, contribution: Contribution): string[] {\n const name = (contribution as any).name as string | undefined;\n if (!name) {\n return [originalTarget];\n }\n\n const appTargets = this.appNameRemaps.get(name);\n if (appTargets) {\n return appTargets.length > 0 ? appTargets : [];\n }\n\n const globalTargets = this.globalNameRemaps.get(name);\n if (globalTargets) {\n return globalTargets.length > 0 ? globalTargets : [];\n }\n\n return [originalTarget];\n }\n\n /**\n * Returns current mapping state for debugging and tooling.\n */\n listNameRemaps(): Record<string, NameRemapState> {\n const result: Record<string, NameRemapState> = {};\n const allNames = new Set<string>([\n ...this.globalNameRemaps.keys(),\n ...this.appNameRemaps.keys()\n ]);\n for (const name of allNames) {\n result[name] = {\n appTargets: this.appNameRemaps.get(name),\n globalTargets: this.globalNameRemaps.get(name)\n };\n }\n return result;\n }\n\n private normalizeTargets(targets: string[]): string[] {\n const seen = new Set<string>();\n const result: string[] = [];\n for (const t of targets) {\n if (!t || seen.has(t)) continue;\n seen.add(t);\n result.push(t);\n }\n return result;\n }\n}\n\nexport const contributionTargetMappingRegistry = new ContributionTargetMappingRegistry();\nrootContext.put(\"contributionTargetMappingRegistry\", contributionTargetMappingRegistry);\n\n/**\n * Public helper to override contribution targets programmatically.\n * This sets a global remap that applies across all apps.\n */\nexport function overrideContributionTargets(name: string, targets: string | string[]): void {\n const normalized = Array.isArray(targets) ? targets : [targets];\n contributionTargetMappingRegistry.setGlobalNameRemap(name, normalized);\n}\n\n/**\n * Apply config-driven name remaps from a simple object.\n * Intended for hosts that want to load JSON/YAML mapping files.\n */\nexport function applyConfigNameRemaps(config: Record<string, string | string[]>): void {\n for (const [name, targets] of Object.entries(config)) {\n const normalized = Array.isArray(targets) ? targets : [targets];\n contributionTargetMappingRegistry.setGlobalNameRemap(name, normalized);\n }\n}\n\n/**\n * Convenience helper for debugging: returns current remap state\n * for all known contribution names.\n */\nexport function listContributionNameRemaps(): Record<string, NameRemapState> {\n return contributionTargetMappingRegistry.listNameRemaps();\n}\n\n","import { Signal } from \"@lit-labs/signals\";\nimport { TemplateResult } from \"lit\";\nimport { publish } from \"./events\";\nimport { rootContext } from \"./di\";\nimport { contributionTargetMappingRegistry } from \"./contribution-mapping\";\n\nexport const TOPIC_CONTRIBUTEIONS_CHANGED = \"events/contributionregistry/contributionsChanged\"\n\nexport interface ContributionChangeEvent {\n target: string;\n contributions: Contribution[];\n}\n\nexport interface Contribution {\n /** Optional globally unique, namespaced contribution name (e.g. \"view.filebrowser\"). */\n name?: string;\n target?: string;\n label: string;\n /** When true, UI may show `label` alongside the icon (e.g. toolbar). Honored where supported in UI. */\n showLabel?: boolean;\n icon?: string;\n slot?: string;\n disabled?: (() => boolean) | Signal.Computed<boolean>;\n /** When false, the contribution is not rendered (default: shown). Honored where supported in UI. */\n visible?: (() => boolean) | Signal.Computed<boolean>;\n}\n\nexport interface CommandContribution extends Contribution {\n command: string;\n params?: Record<string, any>;\n}\n\nexport function isCommandContribution(contribution: Contribution): contribution is CommandContribution {\n return \"command\" in contribution;\n}\n\n/** Default true when `visible` is omitted. */\nexport function getContributionVisible(contribution: Contribution): boolean {\n const visible = contribution.visible as Signal.Computed<boolean> | undefined;\n if (!visible) return true;\n return visible.get() !== false;\n}\n\n/** Default false when `disabled` is omitted. */\nexport function getContributionDisabled(contribution: Contribution): boolean {\n const disabled = contribution.disabled as Signal.Computed<boolean> | undefined;\n if (!disabled) return false;\n return disabled.get() === true;\n}\n\nfunction wrapContributionReactiveFlags(contribution: Contribution): void {\n if (contribution.disabled instanceof Function) {\n contribution.disabled = new Signal.Computed<boolean>(contribution.disabled);\n }\n if (contribution.visible instanceof Function) {\n contribution.visible = new Signal.Computed<boolean>(contribution.visible);\n }\n}\n\nexport interface HTMLContribution extends Contribution {\n component: string | (() => TemplateResult)\n}\n\nexport interface TabContribution extends Contribution {\n name: string;\n /**\n * Stable editor-type identifier used for contribution targets (for example `toolbar:${editorId}`).\n * Needed because `name` is often tab-instance specific (such as file/path based) and would\n * fragment toolbar contributions across many dynamic targets.\n */\n editorId?: string;\n closable?: boolean;\n /** When true or omitted, a docks-toolbar is rendered for this tab; when false, no toolbar is shown. */\n toolbar?: boolean;\n /** When true or omitted, a contextmenu is rendered for this tab; when false, no context menu is shown. */\n contextMenu?: boolean;\n /**\n * Main-area editor type ids (`TabContribution.editorId` of the editor tab) that should reveal this tab\n * when any of them becomes the active editor in the main editor area. Omitted or empty = no auto-reveal.\n */\n coupledEditors?: string[];\n component?: (id: string) => TemplateResult;\n}\n\nexport interface IconContribution extends Contribution {\n mappings: Record<string, string>;\n priority?: number;\n}\n\nexport interface LayoutContribution extends Contribution {\n id: string;\n name: string;\n /** Optional icon name (e.g. Font Awesome) for the layout switcher. */\n icon?: string;\n component: string | { tag: string; attributes?: Record<string, string> } | (() => TemplateResult);\n onShow?: () => void | Promise<void>;\n}\n\nclass ContributionRegistry {\n private contributions: Map<string, Contribution[]> = new Map();\n\n registerContribution<T extends Contribution>(target: string, contribution: T) {\n const targetSlot = this.getOrCreateSlot(target);\n wrapContributionReactiveFlags(contribution);\n targetSlot.push(contribution);\n publish(TOPIC_CONTRIBUTEIONS_CHANGED, { target, contributions: targetSlot } as ContributionChangeEvent);\n\n const effectiveTargets = contributionTargetMappingRegistry.getEffectiveTargets(target, contribution);\n for (const effectiveTarget of effectiveTargets) {\n if (effectiveTarget === target) continue;\n const contributionsForSlot = this.getContributions(effectiveTarget);\n publish(TOPIC_CONTRIBUTEIONS_CHANGED, { target: effectiveTarget, contributions: contributionsForSlot } as ContributionChangeEvent);\n }\n }\n\n /**\n * Returns all contributions whose *effective* targets include the given slot.\n *\n * Note: This currently scans all registered contributions and resolves\n * remaps on each call (O(N) in number of contributions). This is acceptable\n * for typical Docks apps (dozens/hundreds of contributions and few slots),\n * but if contribution counts grow significantly we may want to introduce a\n * cached index of effective targets per slot to keep lookups O(1).\n */\n getContributions<T extends Contribution>(target: string): T[] {\n const results: T[] = [];\n for (const [originalTarget, list] of this.contributions.entries()) {\n const typedList = list as T[];\n for (const contribution of typedList) {\n const effectiveTargets = contributionTargetMappingRegistry.getEffectiveTargets(originalTarget, contribution);\n if (effectiveTargets.includes(target)) {\n results.push(contribution);\n }\n }\n }\n if (results.length === 0) {\n this.getOrCreateSlot(target);\n }\n return results;\n }\n\n private getOrCreateSlot(target: string): Contribution[] {\n if (!this.contributions.has(target)) {\n this.contributions.set(target, []);\n }\n return this.contributions.get(target)!;\n }\n}\n\nexport const contributionRegistry = new ContributionRegistry()\nrootContext.put(\"contributionRegistry\", contributionRegistry)","import { signal } from '@lit-labs/signals';\nimport { DocksPart } from \"../parts/part\";\n\nexport const EMPTY_SIGNALPORT = {} as any\n\nexport const activePartSignal = signal<DocksPart>(null as unknown as DocksPart)\nexport const activeEditorSignal = signal<DocksPart>(null as unknown as DocksPart)\n\nexport const partDirtySignal = signal<DocksPart>(null as unknown as DocksPart)\n\nexport const activeTasksSignal = signal<number>(0)\n\nexport const activeSelectionSignal = signal<any>(undefined)\n\nexport const perspectiveSwitchedSignal = signal<{name: string, timestamp: number}>({name: '', timestamp: 0})","import logger from \"./logger\";\nimport { publish } from \"./events\";\nimport { CommandContribution, Contribution, contributionRegistry } from \"./contributionregistry\";\nimport { rootContext } from \"./di\";\nimport { activePartSignal, activeEditorSignal } from \"./appstate\";\n\nexport const TOPIC_COMMAND_REGISTERED = \"events/commandregistry/commandRegistered\";\n\nexport interface Parameter {\n name: string;\n description: string;\n type?: string;\n required: boolean;\n allowedValues?: string[];\n}\n\nexport interface Variable {\n name: string;\n description: string;\n type?: string;\n}\n\nexport interface LLMOptions {\n prompt?: string;\n model?: string;\n}\n\nexport class Command {\n id: string;\n name: string;\n description: string;\n icon?: string;\n keyBinding?: string;\n parameters?: Parameter[];\n output?: Variable[];\n llm?: LLMOptions;\n\n constructor(id: string, name: string, description: string, parameters?: Parameter[], output?: Variable[]) {\n this.id = id;\n this.name = name;\n this.description = description;\n this.parameters = parameters || [];\n this.output = output || [];\n }\n}\n\nexport interface ExecutionContext {\n [key: string]: any;\n\n params?: ExecuteParams;\n activePart?: any;\n activeEditor?: any;\n}\n\nexport interface ExecuteParams {\n [key: string]: any;\n}\n\nexport interface Handler {\n canExecute?: (context: ExecutionContext) => boolean;\n execute: (context: ExecutionContext) => any | Promise<any>;\n ranking?: number; // Higher ranking = higher priority (default: 0)\n}\n\nexport interface Commands {\n [commandId: string]: Command\n}\n\nfunction formatCommandResultForLog(value: unknown): string {\n if (value === undefined) {\n return \"undefined\";\n }\n if (value === null) {\n return \"null\";\n }\n if (typeof value === \"string\") {\n return value.length > 300 ? `${value.slice(0, 300)}…` : value;\n }\n if (typeof value === \"number\" || typeof value === \"boolean\") {\n return String(value);\n }\n try {\n const s = JSON.stringify(value);\n return s.length > 300 ? `${s.slice(0, 300)}…` : s;\n } catch {\n return String(value);\n }\n}\n\nexport class CommandStack {\n //private stack: any[] = []\n\n public async execute(commandId: any, context: ExecutionContext) {\n return commandRegistry.execute(commandId, context);\n }\n\n public async undo() {\n // TODO undo last command on stack\n }\n\n public async redo() {\n // TODO redo last command on stack\n }\n}\n\nexport class CommandRegistry {\n private commands: Commands = {};\n private handlers: Map<string, Handler[]> = new Map();\n\n registerHandler(commandId: string, handler: Handler) {\n if (!this.handlers.has(commandId)) {\n this.handlers.set(commandId, []);\n }\n const handlerList = this.handlers.get(commandId) as Handler[];\n handlerList.push(handler);\n // Sort by ranking (higher ranking first), default ranking is 0\n handlerList.sort((a, b) => (b.ranking ?? 0) - (a.ranking ?? 0));\n }\n\n getHandler(commandId: string): Handler[] | undefined {\n return this.handlers.get(commandId)\n }\n\n /**\n * Creates an ExecutionContext with the current application state\n * \n * @param params Optional command parameters\n * @returns ExecutionContext with captured application state\n */\n createExecutionContext(params?: ExecuteParams): ExecutionContext {\n const context: ExecutionContext = {\n params: params || {},\n activePart: activePartSignal.get(),\n activeEditor: activeEditorSignal.get()\n };\n\n return context;\n }\n\n async execute(commandId: string, context: ExecutionContext = {}) {\n const handlers = this.getHandler(commandId);\n\n if (!handlers) {\n logger.debug(`[CommandRegistry] No handlers registered for command: ${commandId}`);\n throw new Error(`No handlers registered for command: ${commandId}`);\n }\n\n const command = this.getCommand(commandId);\n const paramsStr = context.params ? ` params: ${JSON.stringify(context.params)}` : '';\n logger.debug(`[CommandRegistry] Executing command: ${commandId}${command ? ` (${command.name})` : ''}${paramsStr}`);\n\n // Handlers are already sorted by ranking, so iterate in order\n for (const handler of handlers) {\n if (handler.canExecute === undefined || handler.canExecute(context)) {\n try {\n const raw = handler.execute(context);\n const result = await Promise.resolve(raw);\n logger.debug(\n `[CommandRegistry] Command executed successfully: ${commandId} (result: ${formatCommandResultForLog(result)})`,\n );\n return result;\n } catch (error) {\n const errorMsg = error instanceof Error ? error.message : String(error);\n logger.error(`[CommandRegistry] Command execution failed: ${commandId} - ${errorMsg}`);\n throw error;\n }\n }\n }\n logger.error(`[CommandRegistry] No handler found to execute command: ${commandId}`);\n }\n\n createAndRegisterCommand(id: string, name: string, description: string, parameters: Parameter[], handler?: Handler) {\n const command = new Command(id, name, description, parameters);\n this.registerCommand(command)\n if (handler) {\n this.registerHandler(id, handler)\n }\n }\n\n registerCommand(command: Command): void {\n this.commands[command.id] = command;\n publish(TOPIC_COMMAND_REGISTERED, command);\n }\n\n hasCommand(commandId: string): boolean {\n return commandId in this.commands\n }\n\n listCommands(context?: ExecutionContext): Commands {\n if (context) {\n // filter by commands which have at least one executable handler\n return Object.values(this.commands).filter(command => {\n const handlers = commandRegistry.getHandler(command.id) || []\n return handlers.some(handler => handler.canExecute === undefined || handler.canExecute(context))\n }).reduce((acc: any, command) => {\n acc[command.id] = command\n return acc\n }, {})\n }\n return this.commands\n }\n\n getCommand(commandId: string): Command {\n return <Command>this.commands[commandId]\n }\n\n registerAll(options: RegisterOptions) {\n const commandId = options.command.id\n this.registerCommand(options.command)\n if (options.handler) {\n this.registerHandler(commandId, options.handler)\n }\n if (options.contribution && options.contribution.target) {\n contributionRegistry.registerContribution(options.contribution.target, {\n command: commandId,\n ...options.contribution\n } as CommandContribution)\n }\n }\n}\n\nexport const commandRegistry = new CommandRegistry();\nrootContext.put(\"commandRegistry\", commandRegistry);\n\nexport interface RegisterOptions {\n command: Command,\n handler?: Handler,\n contribution?: Contribution\n}\n\nexport const registerAll = (options: RegisterOptions) => {\n commandRegistry.registerAll(options);\n}","import { css } from 'lit';\n\n/**\n * Stretch the Web Awesome label slot across the rest of the tree row.\n *\n * By default the slot is only as wide as its content, so clicks (and the context menu’s\n * synthetic click) in the empty area beside a short name often miss the slotted nodes.\n * `wa-tree` only updates selection when mousedown and click targets line up, so those\n * misses leave the wrong row selected. `flex: 1` makes the label column fill the row\n * so hits stay on the item’s content.\n *\n * Applied via `DocksWidget.finalizeStyles`: document-level CSS cannot target `::part()`\n * on `wa-tree-item` that lives only inside nested Lit shadow roots.\n */\nexport const waTreeLabelSlotStretch = css`\n wa-tree-item::part(label) {\n flex: 1;\n min-width: 0;\n }\n`;\n","import {Signal} from \"@lit-labs/signals\";\n\nexport const watchSignal = (signal: Signal.State<any> | Signal.Computed<any>, callback: (value: any) => void): (() => void) => {\n const watcher = new Signal.subtle.Watcher(async () => {\n try {\n await 0;\n callback(signal.get());\n } finally {\n watcher.watch(signal);\n }\n });\n watcher.watch(signal);\n signal.get();\n \n return () => {\n watcher.unwatch(signal);\n };\n}\n","import {LitElement, PropertyValues, type CSSResultGroup} from \"lit\";\nimport {waTreeLabelSlotStretch} from \"../externals/wa-tree-shadow-parts\";\nimport {subscribe as event_subscribe, unsubscribe as event_unsubscribe, type SubscriptionToken} from \"../core/events\";\nimport {toastError, toastInfo} from \"../core/toast\";\nimport {commandRegistry, ExecuteParams} from \"../core/commandregistry\";\nimport {Signal, SignalWatcher} from \"@lit-labs/signals\";\nimport {watchSignal} from \"../core/signals\";\n\nObject.defineProperty(LitElement.prototype, \"model\", {\n enumerable: true,\n configurable: true,\n writable: true\n});\n\nconst DocksWidgetBase = SignalWatcher(LitElement) as unknown as typeof LitElement;\n\nexport abstract class DocksWidget extends DocksWidgetBase {\n static finalizeStyles(styles?: CSSResultGroup) {\n return [waTreeLabelSlotStretch, ...super.finalizeStyles(styles)];\n }\n\n private signalCleanups = new Set<() => void>();\n private eventSubscriptions = new Set<SubscriptionToken>();\n\n connectedCallback() {\n super.connectedCallback();\n this.doBeforeUI()\n }\n\n disconnectedCallback() {\n this.doClose();\n super.disconnectedCallback();\n this.eventSubscriptions.forEach(token => event_unsubscribe(token));\n this.eventSubscriptions.clear();\n this.signalCleanups.forEach(cleanup => cleanup());\n this.signalCleanups.clear();\n }\n\n protected subscribe(topic: string, callback: Function): void {\n const token = event_subscribe(topic, callback.bind(this));\n this.eventSubscriptions.add(token);\n }\n\n protected showInfo(msg: string) {\n toastInfo(msg)\n }\n\n protected showError(msg: string) {\n toastError(msg)\n }\n\n protected nobubble(wrap: (event: Event) => void) {\n return (event: Event) => {\n event.stopPropagation();\n wrap.bind(this)(event);\n };\n }\n\n protected command(command: string, state: {} = {}) {\n return () => {\n void this.executeCommand(command, state);\n };\n }\n\n protected async executeCommand(command: string, params: ExecuteParams) {\n const execContext = commandRegistry.createExecutionContext(params);\n await commandRegistry.execute(command, execContext);\n }\n\n protected watch(signal: Signal.State<any> | Signal.Computed<any>, callback: (value: any) => void): void {\n const cleanup = watchSignal(signal as Signal.State<any>, callback.bind(this));\n this.signalCleanups.add(cleanup);\n }\n\n protected firstUpdated(_changedProperties: PropertyValues) {\n super.firstUpdated(_changedProperties);\n this.doInitUI()\n }\n\n protected updateIdle() {\n requestIdleCallback(() => this.requestUpdate())\n }\n\n public updateLater() {\n requestAnimationFrame(() => this.requestUpdate())\n }\n\n protected doBeforeUI() {\n }\n\n protected doInitUI() {\n }\n\n /**\n * Override to release resources when the element is disconnected (called from {@link disconnectedCallback}).\n */\n protected doClose() {\n }\n\n withRefresh(callback: () => void) {\n callback()\n this.updateLater()\n }\n}\n"],"mappings":";;;;;;;AAIA,IAAM,SAAS,aAAa,QAAQ;AAGpC,IAAM,OAAO;CAAE,UADE;CACkB,SAAS;CAAmB,UAAU;CAAkB,OAAO;CAAM;AAExG,IAAM,QAAQ,KAAa,UAAiD;AAC1E,UAAS;EAAE,GAAG;EAAM,MAAM;EAAK;EAAO,CAAC,CAAC,WAAW;;AAGrD,IAAa,aAAa,QAAgB;AACxC,QAAO,KAAK,IAAI;AAChB,MAAK,KAAK;EAAE,YAAY;EAA4B,OAAO;EAA4B,CAAC;;AAG1F,IAAa,cAAc,QAAgB;AACzC,QAAO,MAAM,IAAI;AACjB,MAAK,KAAK;EAAE,YAAY;EAA6B,OAAO;EAA6B,CAAC;;AAG5F,IAAa,gBAAgB,QAAgB;AAC3C,QAAO,KAAK,IAAI;AAChB,MAAK,KAAK;EAAE,YAAY;EAA8B,OAAO;EAA8B,CAAC;;;;;;;;;;;;;;;ACO9F,IAAM,oCAAN,MAAwC;;0CACT,IAAI,KAAuB;uCAC9B,IAAI,KAAuB;;CAEnD,mBAAmB,MAAc,SAAyB;AACtD,OAAK,iBAAiB,IAAI,MAAM,KAAK,iBAAiB,QAAQ,CAAC;;;CAInE,kBAAwB;AACpB,OAAK,iBAAiB,OAAO;AAC7B,OAAK,cAAc,OAAO;;CAG9B,mBAAmB,QAA0D;AACzE,OAAK,cAAc,OAAO;AAC1B,MAAI,CAAC,OAAQ;AACb,OAAK,MAAM,SAAS,OAChB,MAAK,cAAc,IAAI,MAAM,MAAM,KAAK,iBAAiB,MAAM,QAAQ,CAAC;;;;;;;;;;CAYhF,oBAAoB,gBAAwB,cAAsC;EAC9E,MAAM,OAAQ,aAAqB;AACnC,MAAI,CAAC,KACD,QAAO,CAAC,eAAe;EAG3B,MAAM,aAAa,KAAK,cAAc,IAAI,KAAK;AAC/C,MAAI,WACA,QAAO,WAAW,SAAS,IAAI,aAAa,EAAE;EAGlD,MAAM,gBAAgB,KAAK,iBAAiB,IAAI,KAAK;AACrD,MAAI,cACA,QAAO,cAAc,SAAS,IAAI,gBAAgB,EAAE;AAGxD,SAAO,CAAC,eAAe;;;;;CAM3B,iBAAiD;EAC7C,MAAM,SAAyC,EAAE;EACjD,MAAM,WAAW,IAAI,IAAY,CAC7B,GAAG,KAAK,iBAAiB,MAAM,EAC/B,GAAG,KAAK,cAAc,MAAM,CAC/B,CAAC;AACF,OAAK,MAAM,QAAQ,SACf,QAAO,QAAQ;GACX,YAAY,KAAK,cAAc,IAAI,KAAK;GACxC,eAAe,KAAK,iBAAiB,IAAI,KAAK;GACjD;AAEL,SAAO;;CAGX,iBAAyB,SAA6B;EAClD,MAAM,uBAAO,IAAI,KAAa;EAC9B,MAAM,SAAmB,EAAE;AAC3B,OAAK,MAAM,KAAK,SAAS;AACrB,OAAI,CAAC,KAAK,KAAK,IAAI,EAAE,CAAE;AACvB,QAAK,IAAI,EAAE;AACX,UAAO,KAAK,EAAE;;AAElB,SAAO;;;AAIf,IAAa,oCAAoC,IAAI,mCAAmC;AACxF,YAAY,IAAI,qCAAqC,kCAAkC;;;;;AAMvF,SAAgB,4BAA4B,MAAc,SAAkC;CACxF,MAAM,aAAa,MAAM,QAAQ,QAAQ,GAAG,UAAU,CAAC,QAAQ;AAC/D,mCAAkC,mBAAmB,MAAM,WAAW;;;;;;AAO1E,SAAgB,sBAAsB,QAAiD;AACnF,MAAK,MAAM,CAAC,MAAM,YAAY,OAAO,QAAQ,OAAO,EAAE;EAClD,MAAM,aAAa,MAAM,QAAQ,QAAQ,GAAG,UAAU,CAAC,QAAQ;AAC/D,oCAAkC,mBAAmB,MAAM,WAAW;;;;;;;AAQ9E,SAAgB,6BAA6D;AACzE,QAAO,kCAAkC,gBAAgB;;;;ACrI7D,IAAa,+BAA+B;;AA+B5C,SAAgB,uBAAuB,cAAqC;CACxE,MAAM,UAAU,aAAa;AAC7B,KAAI,CAAC,QAAS,QAAO;AACrB,QAAO,QAAQ,KAAK,KAAK;;;AAI7B,SAAgB,wBAAwB,cAAqC;CACzE,MAAM,WAAW,aAAa;AAC9B,KAAI,CAAC,SAAU,QAAO;AACtB,QAAO,SAAS,KAAK,KAAK;;AAG9B,SAAS,8BAA8B,cAAkC;AACrE,KAAI,aAAa,oBAAoB,SACjC,cAAa,WAAW,IAAI,OAAO,SAAkB,aAAa,SAAS;AAE/E,KAAI,aAAa,mBAAmB,SAChC,cAAa,UAAU,IAAI,OAAO,SAAkB,aAAa,QAAQ;;AA2CjF,IAAM,uBAAN,MAA2B;;uCAC8B,IAAI,KAAK;;CAE9D,qBAA6C,QAAgB,cAAiB;EAC1E,MAAM,aAAa,KAAK,gBAAgB,OAAO;AAC/C,gCAA8B,aAAa;AAC3C,aAAW,KAAK,aAAa;AAC7B,UAAQ,8BAA8B;GAAE;GAAQ,eAAe;GAAY,CAA4B;EAEvG,MAAM,mBAAmB,kCAAkC,oBAAoB,QAAQ,aAAa;AACpG,OAAK,MAAM,mBAAmB,kBAAkB;AAC5C,OAAI,oBAAoB,OAAQ;AAEhC,WAAQ,8BAA8B;IAAE,QAAQ;IAAiB,eADpC,KAAK,iBAAiB,gBAAgB;IACmC,CAA4B;;;;;;;;;;;;CAa1I,iBAAyC,QAAqB;EAC1D,MAAM,UAAe,EAAE;AACvB,OAAK,MAAM,CAAC,gBAAgB,SAAS,KAAK,cAAc,SAAS,EAAE;GAC/D,MAAM,YAAY;AAClB,QAAK,MAAM,gBAAgB,UAEvB,KADyB,kCAAkC,oBAAoB,gBAAgB,aAAa,CACvF,SAAS,OAAO,CACjC,SAAQ,KAAK,aAAa;;AAItC,MAAI,QAAQ,WAAW,EACnB,MAAK,gBAAgB,OAAO;AAEhC,SAAO;;CAGX,gBAAwB,QAAgC;AACpD,MAAI,CAAC,KAAK,cAAc,IAAI,OAAO,CAC/B,MAAK,cAAc,IAAI,QAAQ,EAAE,CAAC;AAEtC,SAAO,KAAK,cAAc,IAAI,OAAO;;;AAI7C,IAAa,uBAAuB,IAAI,sBAAsB;AAC9D,YAAY,IAAI,wBAAwB,qBAAA;;;ACnJxC,IAAa,mBAAmB,EAAE;AAElC,IAAa,mBAAmB,OAAkB,KAA6B;AAC/E,IAAa,qBAAqB,OAAkB,KAA6B;AAEjF,IAAa,kBAAkB,OAAkB,KAA6B;AAE9E,IAAa,oBAAoB,OAAe,EAAE;AAElD,IAAa,wBAAwB,OAAY,KAAA,EAAU;AAE3D,IAAa,4BAA4B,OAA0C;CAAC,MAAM;CAAI,WAAW;CAAE,CAAA;;;ACR3G,IAAa,2BAA2B;AAqBxC,IAAa,UAAb,MAAqB;CAUjB,YAAY,IAAY,MAAc,aAAqB,YAA0B,QAAqB;AACtG,OAAK,KAAK;AACV,OAAK,OAAO;AACZ,OAAK,cAAc;AACnB,OAAK,aAAa,cAAc,EAAE;AAClC,OAAK,SAAS,UAAU,EAAE;;;AA0BlC,SAAS,0BAA0B,OAAwB;AACvD,KAAI,UAAU,KAAA,EACV,QAAO;AAEX,KAAI,UAAU,KACV,QAAO;AAEX,KAAI,OAAO,UAAU,SACjB,QAAO,MAAM,SAAS,MAAM,GAAG,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK;AAE5D,KAAI,OAAO,UAAU,YAAY,OAAO,UAAU,UAC9C,QAAO,OAAO,MAAM;AAExB,KAAI;EACA,MAAM,IAAI,KAAK,UAAU,MAAM;AAC/B,SAAO,EAAE,SAAS,MAAM,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC,KAAK;SAC5C;AACJ,SAAO,OAAO,MAAM;;;AAI5B,IAAa,eAAb,MAA0B;CAGtB,MAAa,QAAQ,WAAgB,SAA2B;AAC5D,SAAO,gBAAgB,QAAQ,WAAW,QAAQ;;CAGtD,MAAa,OAAO;CAIpB,MAAa,OAAO;;AAKxB,IAAa,kBAAb,MAA6B;;kBACI,EAAE;kCACY,IAAI,KAAK;;CAEpD,gBAAgB,WAAmB,SAAkB;AACjD,MAAI,CAAC,KAAK,SAAS,IAAI,UAAU,CAC7B,MAAK,SAAS,IAAI,WAAW,EAAE,CAAC;EAEpC,MAAM,cAAc,KAAK,SAAS,IAAI,UAAU;AAChD,cAAY,KAAK,QAAQ;AAEzB,cAAY,MAAM,GAAG,OAAO,EAAE,WAAW,MAAM,EAAE,WAAW,GAAG;;CAGnE,WAAW,WAA0C;AACjD,SAAO,KAAK,SAAS,IAAI,UAAU;;;;;;;;CASvC,uBAAuB,QAA0C;AAO7D,SANkC;GAC9B,QAAQ,UAAU,EAAE;GACpB,YAAY,iBAAiB,KAAK;GAClC,cAAc,mBAAmB,KAAK;GACzC;;CAKL,MAAM,QAAQ,WAAmB,UAA4B,EAAE,EAAE;EAC7D,MAAM,WAAW,KAAK,WAAW,UAAU;AAE3C,MAAI,CAAC,UAAU;AACX,iBAAO,MAAM,yDAAyD,YAAY;AAClF,SAAM,IAAI,MAAM,uCAAuC,YAAY;;EAGvE,MAAM,UAAU,KAAK,WAAW,UAAU;EAC1C,MAAM,YAAY,QAAQ,SAAS,YAAY,KAAK,UAAU,QAAQ,OAAO,KAAK;AAClF,gBAAO,MAAM,wCAAwC,YAAY,UAAU,KAAK,QAAQ,KAAK,KAAK,KAAK,YAAY;AAGnH,OAAK,MAAM,WAAW,SAClB,KAAI,QAAQ,eAAe,KAAA,KAAa,QAAQ,WAAW,QAAQ,CAC/D,KAAI;GACA,MAAM,MAAM,QAAQ,QAAQ,QAAQ;GACpC,MAAM,SAAS,MAAM,QAAQ,QAAQ,IAAI;AACzC,iBAAO,MACH,oDAAoD,UAAU,YAAY,0BAA0B,OAAO,CAAC,GAC/G;AACD,UAAO;WACF,OAAO;GACZ,MAAM,WAAW,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM;AACvE,iBAAO,MAAM,+CAA+C,UAAU,KAAK,WAAW;AACtF,SAAM;;AAIlB,gBAAO,MAAM,0DAA0D,YAAY;;CAGvF,yBAAyB,IAAY,MAAc,aAAqB,YAAyB,SAAmB;EAChH,MAAM,UAAU,IAAI,QAAQ,IAAI,MAAM,aAAa,WAAW;AAC9D,OAAK,gBAAgB,QAAQ;AAC7B,MAAI,QACA,MAAK,gBAAgB,IAAI,QAAQ;;CAIzC,gBAAgB,SAAwB;AACpC,OAAK,SAAS,QAAQ,MAAM;AAC5B,UAAQ,0BAA0B,QAAQ;;CAG9C,WAAW,WAA4B;AACnC,SAAO,aAAa,KAAK;;CAG7B,aAAa,SAAsC;AAC/C,MAAI,QAEA,QAAO,OAAO,OAAO,KAAK,SAAS,CAAC,QAAO,YAAW;AAElD,WADiB,gBAAgB,WAAW,QAAQ,GAAG,IAAI,EAAE,EAC7C,MAAK,YAAW,QAAQ,eAAe,KAAA,KAAa,QAAQ,WAAW,QAAQ,CAAC;IAClG,CAAC,QAAQ,KAAU,YAAY;AAC7B,OAAI,QAAQ,MAAM;AAClB,UAAO;KACR,EAAE,CAAC;AAEV,SAAO,KAAK;;CAGhB,WAAW,WAA4B;AACnC,SAAgB,KAAK,SAAS;;CAGlC,YAAY,SAA0B;EAClC,MAAM,YAAY,QAAQ,QAAQ;AAClC,OAAK,gBAAgB,QAAQ,QAAQ;AACrC,MAAI,QAAQ,QACR,MAAK,gBAAgB,WAAW,QAAQ,QAAQ;AAEpD,MAAI,QAAQ,gBAAgB,QAAQ,aAAa,OAC7C,sBAAqB,qBAAqB,QAAQ,aAAa,QAAQ;GACnE,SAAS;GACT,GAAG,QAAQ;GACd,CAAwB;;;AAKrC,IAAa,kBAAkB,IAAI,iBAAiB;AACpD,YAAY,IAAI,mBAAmB,gBAAgB;AAQnD,IAAa,eAAe,YAA6B;AACrD,iBAAgB,YAAY,QAAQ;;;;;;;;;;;;;;;;ACzNxC,IAAa,yBAAyB,GAAG;;;;;;;;ACZzC,IAAa,eAAe,QAAkD,aAAiD;CAC3H,MAAM,UAAU,IAAI,OAAO,OAAO,QAAQ,YAAY;AAClD,MAAI;AACA,SAAM;AACN,YAAS,OAAO,KAAK,CAAC;YAChB;AACN,WAAQ,MAAM,OAAO;;GAE3B;AACF,SAAQ,MAAM,OAAO;AACrB,QAAO,KAAK;AAEZ,cAAa;AACT,UAAQ,QAAQ,OAAO;;;;;ACP/B,OAAO,eAAe,WAAW,WAAW,SAAS;CACjD,YAAY;CACZ,cAAc;CACd,UAAU;CACb,CAAC;AAEF,IAAM,kBAAkB,cAAc,WAAW;AAEjD,IAAsB,cAAtB,cAA0C,gBAAgB;;;wCAK7B,IAAI,KAAiB;4CACjB,IAAI,KAAwB;;CALzD,OAAO,eAAe,QAAyB;AAC3C,SAAO,CAAC,wBAAwB,GAAG,MAAM,eAAe,OAAO,CAAC;;CAMpE,oBAAoB;AAChB,QAAM,mBAAmB;AACzB,OAAK,YAAY;;CAGrB,uBAAuB;AACnB,OAAK,SAAS;AACd,QAAM,sBAAsB;AAC5B,OAAK,mBAAmB,SAAQ,UAAS,YAAkB,MAAM,CAAC;AAClE,OAAK,mBAAmB,OAAO;AAC/B,OAAK,eAAe,SAAQ,YAAW,SAAS,CAAC;AACjD,OAAK,eAAe,OAAO;;CAG/B,UAAoB,OAAe,UAA0B;EACzD,MAAM,QAAQ,UAAgB,OAAO,SAAS,KAAK,KAAK,CAAC;AACzD,OAAK,mBAAmB,IAAI,MAAM;;CAGtC,SAAmB,KAAa;AAC5B,YAAU,IAAI;;CAGlB,UAAoB,KAAa;AAC7B,aAAW,IAAI;;CAGnB,SAAmB,MAA8B;AAC7C,UAAQ,UAAiB;AACrB,SAAM,iBAAiB;AACvB,QAAK,KAAK,KAAK,CAAC,MAAM;;;CAI9B,QAAkB,SAAiB,QAAY,EAAE,EAAE;AAC/C,eAAa;AACJ,QAAK,eAAe,SAAS,MAAM;;;CAIhD,MAAgB,eAAe,SAAiB,QAAuB;EACnE,MAAM,cAAc,gBAAgB,uBAAuB,OAAO;AAClE,QAAM,gBAAgB,QAAQ,SAAS,YAAY;;CAGvD,MAAgB,QAAkD,UAAsC;EACpG,MAAM,UAAU,YAAY,QAA6B,SAAS,KAAK,KAAK,CAAC;AAC7E,OAAK,eAAe,IAAI,QAAQ;;CAGpC,aAAuB,oBAAoC;AACvD,QAAM,aAAa,mBAAmB;AACtC,OAAK,UAAU;;CAGnB,aAAuB;AACnB,4BAA0B,KAAK,eAAe,CAAC;;CAGnD,cAAqB;AACjB,8BAA4B,KAAK,eAAe,CAAC;;CAGrD,aAAuB;CAGvB,WAAqB;;;;CAMrB,UAAoB;CAGpB,YAAY,UAAsB;AAC9B,YAAU;AACV,OAAK,aAAa"}
@@ -1 +1 @@
1
- import "../widgets-Cgi8WuOZ.js";
1
+ import "../widgets-SScpQQr9.js";
@@ -1,4 +1,4 @@
1
- import { t as DocksWidget } from "./widget-BgBerzhp.js";
1
+ import { t as DocksWidget } from "./widget-DbolQH0C.js";
2
2
  import { css, html } from "lit";
3
3
  import { customElement, property } from "lit/decorators.js";
4
4
  import _decorate from "@oxc-project/runtime/helpers/decorate";
@@ -69,4 +69,4 @@ _decorate([property()], DocksNoContent.prototype, "icon", void 0);
69
69
  DocksNoContent = _decorate([customElement("docks-no-content")], DocksNoContent);
70
70
  //#endregion
71
71
 
72
- //# sourceMappingURL=widgets-Cgi8WuOZ.js.map
72
+ //# sourceMappingURL=widgets-SScpQQr9.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"widgets-Cgi8WuOZ.js","names":[],"sources":["../src/widgets/nocontent.ts"],"sourcesContent":["import { css, html } from \"lit\";\nimport { customElement, property } from \"lit/decorators.js\";\n\nimport { DocksWidget } from \"./widget\";\n\n@customElement(\"docks-no-content\")\nexport class DocksNoContent extends DocksWidget {\n @property()\n private message: string = \"No content.\";\n\n @property()\n private icon: string = \"info-circle\";\n\n render() {\n return html`\n <div class=\"empty\" role=\"status\">\n <div class=\"empty-icon-wrap\" aria-hidden=\"true\">\n <wa-icon name=${this.icon} label=\"\"></wa-icon>\n </div>\n <p class=\"empty-message\">${this.message}</p>\n </div>\n `;\n }\n\n static styles = css`\n :host {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 100%;\n height: 100%;\n box-sizing: border-box;\n padding: var(--wa-space-xl) var(--wa-space-l);\n }\n\n .empty {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n gap: var(--wa-space-m);\n max-width: min(28rem, 100%);\n text-align: center;\n }\n\n .empty-icon-wrap {\n display: flex;\n align-items: center;\n justify-content: center;\n line-height: 1;\n color: var(--wa-color-neutral-40);\n }\n\n .empty-icon-wrap wa-icon {\n font-size: 2rem;\n opacity: 0.9;\n }\n\n .empty-message {\n margin: 0;\n font-size: var(--wa-font-size-s);\n font-weight: 400;\n line-height: 1.5;\n color: var(--wa-color-neutral-60);\n }\n `;\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n \"docks-no-content\": DocksNoContent;\n }\n}\n"],"mappings":";;;;;AAMO,IAAA,iBAAA,MAAM,uBAAuB,YAAY;;;iBAElB;cAGH;;CAEvB,SAAS;AACL,SAAO,IAAI;;;oCAGiB,KAAK,KAAK;;2CAEH,KAAK,QAAQ;;;;;gBAKpC,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;WAjBlB,UAAU,CAAA,EAAA,eAAA,WAAA,WAAA,KAAA,EAAA;WAGV,UAAU,CAAA,EAAA,eAAA,WAAA,QAAA,KAAA,EAAA;4BALd,cAAc,mBAAmB,CAAA,EAAA,eAAA"}
1
+ {"version":3,"file":"widgets-SScpQQr9.js","names":[],"sources":["../src/widgets/nocontent.ts"],"sourcesContent":["import { css, html } from \"lit\";\nimport { customElement, property } from \"lit/decorators.js\";\n\nimport { DocksWidget } from \"./widget\";\n\n@customElement(\"docks-no-content\")\nexport class DocksNoContent extends DocksWidget {\n @property()\n private message: string = \"No content.\";\n\n @property()\n private icon: string = \"info-circle\";\n\n render() {\n return html`\n <div class=\"empty\" role=\"status\">\n <div class=\"empty-icon-wrap\" aria-hidden=\"true\">\n <wa-icon name=${this.icon} label=\"\"></wa-icon>\n </div>\n <p class=\"empty-message\">${this.message}</p>\n </div>\n `;\n }\n\n static styles = css`\n :host {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 100%;\n height: 100%;\n box-sizing: border-box;\n padding: var(--wa-space-xl) var(--wa-space-l);\n }\n\n .empty {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n gap: var(--wa-space-m);\n max-width: min(28rem, 100%);\n text-align: center;\n }\n\n .empty-icon-wrap {\n display: flex;\n align-items: center;\n justify-content: center;\n line-height: 1;\n color: var(--wa-color-neutral-40);\n }\n\n .empty-icon-wrap wa-icon {\n font-size: 2rem;\n opacity: 0.9;\n }\n\n .empty-message {\n margin: 0;\n font-size: var(--wa-font-size-s);\n font-weight: 400;\n line-height: 1.5;\n color: var(--wa-color-neutral-60);\n }\n `;\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n \"docks-no-content\": DocksNoContent;\n }\n}\n"],"mappings":";;;;;AAMO,IAAA,iBAAA,MAAM,uBAAuB,YAAY;;;iBAElB;cAGH;;CAEvB,SAAS;AACL,SAAO,IAAI;;;oCAGiB,KAAK,KAAK;;2CAEH,KAAK,QAAQ;;;;;gBAKpC,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;WAjBlB,UAAU,CAAA,EAAA,eAAA,WAAA,WAAA,KAAA,EAAA;WAGV,UAAU,CAAA,EAAA,eAAA,WAAA,QAAA,KAAA,EAAA;4BALd,cAAc,mBAAmB,CAAA,EAAA,eAAA"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eclipse-docks/core",
3
- "version": "0.7.107",
3
+ "version": "0.7.109",
4
4
  "description": "Eclipse Docks platform core: registries, services, parts, widgets, and API",
5
5
  "type": "module",
6
6
  "license": "EPL-2.0",
@@ -35,8 +35,14 @@ export {
35
35
  navigableInfoDialog,
36
36
  confirmDialog,
37
37
  promptDialog,
38
+ formDialog,
39
+ formDialogRequired,
40
+ validateFormDialogFields,
38
41
  filebrowserDialog,
39
- type NavigableDialogAction
42
+ type NavigableDialogAction,
43
+ type FormDialogField,
44
+ type FormDialogFieldType,
45
+ type FormDialogOptions,
40
46
  } from '../dialogs';
41
47
  export {
42
48
  toastInfo,
package/src/api/types.ts CHANGED
@@ -36,7 +36,10 @@ export type {
36
36
  export type {
37
37
  NavigableDialogAction,
38
38
  FilebrowserDialogMode,
39
- FilebrowserDialogState
39
+ FilebrowserDialogState,
40
+ FormDialogField,
41
+ FormDialogFieldType,
42
+ FormDialogOptions,
40
43
  } from '../dialogs';
41
44
  export type {
42
45
  TreeNode,
@@ -3,9 +3,17 @@ import { customElement, property, state } from 'lit/decorators.js'
3
3
  import { DocksWidget } from '../widgets/widget'
4
4
  import { icon } from '../core/icon-utils'
5
5
  import { keyBindingManager } from '../core/keybindings'
6
- import { contributionRegistry, Contribution, CommandContribution, HTMLContribution, ContributionChangeEvent, TOPIC_CONTRIBUTEIONS_CHANGED } from '../core/contributionregistry'
6
+ import {
7
+ contributionRegistry,
8
+ Contribution,
9
+ CommandContribution,
10
+ HTMLContribution,
11
+ ContributionChangeEvent,
12
+ TOPIC_CONTRIBUTEIONS_CHANGED,
13
+ getContributionDisabled,
14
+ getContributionVisible,
15
+ } from '../core/contributionregistry'
7
16
  import { subscribe } from '../core/events'
8
- import { Signal } from '@lit-labs/signals'
9
17
  import { unsafeHTML } from 'lit/directives/unsafe-html.js'
10
18
 
11
19
  @customElement('docks-command')
@@ -114,7 +122,10 @@ export class DocksCommand extends DocksWidget {
114
122
  private renderContribution(contribution: Contribution) {
115
123
  if ('command' in contribution) {
116
124
  const commandContribution = contribution as CommandContribution
117
- const disabled = (commandContribution.disabled as Signal.Computed<boolean>)?.get()
125
+ if (!getContributionVisible(commandContribution)) {
126
+ return nothing
127
+ }
128
+ const disabled = getContributionDisabled(commandContribution)
118
129
  return html`
119
130
  <docks-command
120
131
  cmd="${commandContribution.command}"
@@ -225,7 +225,7 @@ rootContext.put("commandRegistry", commandRegistry);
225
225
  export interface RegisterOptions {
226
226
  command: Command,
227
227
  handler?: Handler,
228
- contribution?: Contribution & Partial<Pick<CommandContribution, 'disabled'>>
228
+ contribution?: Contribution
229
229
  }
230
230
 
231
231
  export const registerAll = (options: RegisterOptions) => {
@@ -16,15 +16,45 @@ export interface Contribution {
16
16
  name?: string;
17
17
  target?: string;
18
18
  label: string;
19
+ /** When true, UI may show `label` alongside the icon (e.g. toolbar). Honored where supported in UI. */
20
+ showLabel?: boolean;
19
21
  icon?: string;
20
22
  slot?: string;
23
+ disabled?: (() => boolean) | Signal.Computed<boolean>;
24
+ /** When false, the contribution is not rendered (default: shown). Honored where supported in UI. */
25
+ visible?: (() => boolean) | Signal.Computed<boolean>;
21
26
  }
22
27
 
23
28
  export interface CommandContribution extends Contribution {
24
29
  command: string;
25
30
  params?: Record<string, any>;
26
- showLabel?: boolean;
27
- disabled?: (() => boolean) | Signal.Computed<boolean>;
31
+ }
32
+
33
+ export function isCommandContribution(contribution: Contribution): contribution is CommandContribution {
34
+ return "command" in contribution;
35
+ }
36
+
37
+ /** Default true when `visible` is omitted. */
38
+ export function getContributionVisible(contribution: Contribution): boolean {
39
+ const visible = contribution.visible as Signal.Computed<boolean> | undefined;
40
+ if (!visible) return true;
41
+ return visible.get() !== false;
42
+ }
43
+
44
+ /** Default false when `disabled` is omitted. */
45
+ export function getContributionDisabled(contribution: Contribution): boolean {
46
+ const disabled = contribution.disabled as Signal.Computed<boolean> | undefined;
47
+ if (!disabled) return false;
48
+ return disabled.get() === true;
49
+ }
50
+
51
+ function wrapContributionReactiveFlags(contribution: Contribution): void {
52
+ if (contribution.disabled instanceof Function) {
53
+ contribution.disabled = new Signal.Computed<boolean>(contribution.disabled);
54
+ }
55
+ if (contribution.visible instanceof Function) {
56
+ contribution.visible = new Signal.Computed<boolean>(contribution.visible);
57
+ }
28
58
  }
29
59
 
30
60
  export interface HTMLContribution extends Contribution {
@@ -71,12 +101,7 @@ class ContributionRegistry {
71
101
 
72
102
  registerContribution<T extends Contribution>(target: string, contribution: T) {
73
103
  const targetSlot = this.getOrCreateSlot(target);
74
- if ("command" in contribution) {
75
- const cmd = contribution as unknown as CommandContribution
76
- if (cmd.disabled instanceof Function) {
77
- cmd.disabled = new Signal.Computed<boolean>(cmd.disabled)
78
- }
79
- }
104
+ wrapContributionReactiveFlags(contribution);
80
105
  targetSlot.push(contribution);
81
106
  publish(TOPIC_CONTRIBUTEIONS_CHANGED, { target, contributions: targetSlot } as ContributionChangeEvent);
82
107
 
@@ -182,8 +182,13 @@ class DialogService {
182
182
  }
183
183
  const stateWithClose = { ...state, close: cleanup };
184
184
 
185
+ const dialogLabel =
186
+ state && typeof state === 'object' && typeof (state as { label?: unknown }).label === 'string'
187
+ ? (state as { label: string }).label
188
+ : (contribution.label || dialogId);
189
+
185
190
  const template = html`
186
- <wa-dialog label="${contribution.label || dialogId}" open @wa-request-close=${cleanup}>
191
+ <wa-dialog label="${dialogLabel}" open @wa-request-close=${cleanup}>
187
192
  <style>
188
193
  .dialog-service-content {
189
194
  display: flex;
@@ -0,0 +1,239 @@
1
+ import { html, css, PropertyValues } from "lit";
2
+ import { customElement, state, property } from "lit/decorators.js";
3
+ import { DocksDialogContent } from "../parts/dialog-content";
4
+ import { contributionRegistry } from "../core/contributionregistry";
5
+ import {
6
+ DIALOG_CONTRIBUTION_TARGET,
7
+ OK_BUTTON,
8
+ CANCEL_BUTTON,
9
+ dialogService,
10
+ } from "../core/dialogservice";
11
+
12
+ /** HTML input types supported by {@link formDialog}. */
13
+ export type FormDialogFieldType = "text" | "password" | "url" | "email" | "number";
14
+
15
+ export interface FormDialogField {
16
+ /** Key in the returned values record. */
17
+ name: string;
18
+ label: string;
19
+ type?: FormDialogFieldType;
20
+ value?: string;
21
+ placeholder?: string;
22
+ /** When false, empty values are allowed. Defaults to true. */
23
+ required?: boolean;
24
+ }
25
+
26
+ export interface FormDialogOptions {
27
+ /** Dialog window title (shown on `wa-dialog`). */
28
+ label: string;
29
+ fields: FormDialogField[];
30
+ /** Optional message above the fields (plain text or markdown). */
31
+ message?: string;
32
+ markdown?: boolean;
33
+ }
34
+
35
+ interface FormDialogState extends FormDialogOptions {
36
+ resolve: (values: Record<string, string> | null) => void;
37
+ }
38
+
39
+ @customElement("docks-form-dialog-content")
40
+ export class DocksFormDialogContent extends DocksDialogContent {
41
+ @property({ attribute: false })
42
+ fields: FormDialogField[] = [];
43
+
44
+ @property({ type: String })
45
+ message = "";
46
+
47
+ @property({ type: Boolean })
48
+ markdown = false;
49
+
50
+ @state()
51
+ private values: Record<string, string> = {};
52
+
53
+ @state()
54
+ private validationError = "";
55
+
56
+ static styles = [
57
+ ...DocksDialogContent.styles,
58
+ css`
59
+ .form-fields {
60
+ display: flex;
61
+ flex-direction: column;
62
+ gap: 0.75rem;
63
+ min-width: 22rem;
64
+ }
65
+
66
+ .field label {
67
+ display: flex;
68
+ flex-direction: column;
69
+ gap: 0.25rem;
70
+ font-size: var(--wa-font-size-s);
71
+ }
72
+
73
+ .field-label {
74
+ font-weight: 500;
75
+ color: var(--wa-color-text-normal);
76
+ }
77
+
78
+ wa-input {
79
+ width: 100%;
80
+ }
81
+
82
+ .validation-error {
83
+ color: var(--wa-color-danger-600, #dc2626);
84
+ font-size: var(--wa-font-size-s);
85
+ }
86
+ `,
87
+ ];
88
+
89
+ async firstUpdated(changedProperties: PropertyValues) {
90
+ super.firstUpdated(changedProperties);
91
+ const initial: Record<string, string> = {};
92
+ for (const field of this.fields) {
93
+ initial[field.name] = field.value ?? "";
94
+ }
95
+ this.values = initial;
96
+
97
+ await this.updateComplete;
98
+ const input = this.shadowRoot?.querySelector("wa-input");
99
+ const inputEl = (input as HTMLElement & { shadowRoot?: ShadowRoot })?.shadowRoot?.querySelector(
100
+ "input",
101
+ );
102
+ inputEl?.focus();
103
+ }
104
+
105
+ getResult(): Record<string, string> {
106
+ return { ...this.values };
107
+ }
108
+
109
+ setValidationError(message: string): void {
110
+ this.validationError = message;
111
+ }
112
+
113
+ private updateField(name: string, value: string): void {
114
+ this.values = { ...this.values, [name]: value };
115
+ if (this.validationError) this.validationError = "";
116
+ }
117
+
118
+ private handleKeyDown(event: KeyboardEvent): void {
119
+ if (event.key === "Enter") {
120
+ event.preventDefault();
121
+ this.dispatchEvent(new CustomEvent("dialog-ok", { bubbles: true, composed: true }));
122
+ } else if (event.key === "Escape") {
123
+ event.preventDefault();
124
+ this.dispatchEvent(new CustomEvent("dialog-cancel", { bubbles: true, composed: true }));
125
+ }
126
+ }
127
+
128
+ render() {
129
+ return html`
130
+ ${this.message ? this.renderMessage(this.message, this.markdown) : null}
131
+ <div class="form-fields" @keydown=${this.handleKeyDown}>
132
+ ${this.fields.map(
133
+ (field) => html`
134
+ <div class="field">
135
+ <label>
136
+ <span class="field-label">${field.label}</span>
137
+ <wa-input
138
+ type=${field.type ?? "text"}
139
+ .value=${this.values[field.name] ?? ""}
140
+ placeholder=${field.placeholder ?? ""}
141
+ @input=${(event: Event) =>
142
+ this.updateField(
143
+ field.name,
144
+ (event.target as HTMLInputElement).value,
145
+ )}
146
+ ></wa-input>
147
+ </label>
148
+ </div>
149
+ `,
150
+ )}
151
+ ${this.validationError
152
+ ? html`<div class="validation-error">${this.validationError}</div>`
153
+ : null}
154
+ </div>
155
+ `;
156
+ }
157
+ }
158
+
159
+ declare global {
160
+ interface HTMLElementTagNameMap {
161
+ "docks-form-dialog-content": DocksFormDialogContent;
162
+ }
163
+ }
164
+
165
+ function trimValues(values: Record<string, string>): Record<string, string> {
166
+ return Object.fromEntries(
167
+ Object.entries(values).map(([key, value]) => [key, value.trim()]),
168
+ );
169
+ }
170
+
171
+ function findFormDialogContent(): DocksFormDialogContent | null {
172
+ return document.querySelector("docks-form-dialog-content");
173
+ }
174
+
175
+ /** Validates required fields; returns an error message or null if valid. */
176
+ export function validateFormDialogFields(
177
+ fields: FormDialogField[],
178
+ values: Record<string, string>,
179
+ ): string | null {
180
+ for (const field of fields) {
181
+ if (field.required === false) continue;
182
+ if (!values[field.name]?.trim()) {
183
+ return `${field.label} is required.`;
184
+ }
185
+ }
186
+ return null;
187
+ }
188
+
189
+ contributionRegistry.registerContribution(DIALOG_CONTRIBUTION_TARGET, {
190
+ id: "form",
191
+ label: "Form",
192
+ buttons: [OK_BUTTON, CANCEL_BUTTON],
193
+ component: (state?: FormDialogState) => {
194
+ if (!state) {
195
+ return html`<div>Error: No form dialog state</div>`;
196
+ }
197
+
198
+ return html`
199
+ <docks-form-dialog-content
200
+ .message=${state.message ?? ""}
201
+ .markdown=${state.markdown ?? false}
202
+ .fields=${state.fields}
203
+ ></docks-form-dialog-content>
204
+ `;
205
+ },
206
+ onButton: async (id: string, result: unknown, state?: FormDialogState) => {
207
+ if (!state) {
208
+ return true;
209
+ }
210
+
211
+ if (id === "ok") {
212
+ const values = trimValues((result ?? {}) as Record<string, string>);
213
+ const error = validateFormDialogFields(state.fields, values);
214
+ if (error) {
215
+ findFormDialogContent()?.setValidationError(error);
216
+ return false;
217
+ }
218
+ state.resolve(values);
219
+ } else {
220
+ state.resolve(null);
221
+ }
222
+
223
+ return true;
224
+ },
225
+ });
226
+
227
+ export function formDialog(options: FormDialogOptions): Promise<Record<string, string> | null> {
228
+ return new Promise((resolve) => {
229
+ dialogService.open("form", { ...options, resolve });
230
+ });
231
+ }
232
+
233
+ export async function formDialogRequired(
234
+ options: FormDialogOptions,
235
+ ): Promise<Record<string, string>> {
236
+ const values = await formDialog(options);
237
+ if (!values) throw new Error("Cancelled");
238
+ return values;
239
+ }
@@ -1,4 +1,12 @@
1
1
  export { promptDialog } from './prompt-dialog';
2
+ export {
3
+ formDialog,
4
+ formDialogRequired,
5
+ validateFormDialogFields,
6
+ type FormDialogField,
7
+ type FormDialogFieldType,
8
+ type FormDialogOptions,
9
+ } from './form-dialog';
2
10
  export { infoDialog } from './info-dialog';
3
11
  export { confirmDialog } from './confirm-dialog';
4
12
  export { navigableInfoDialog, type NavigableDialogAction } from './navigable-info-dialog';
@@ -6,10 +6,11 @@ import {
6
6
  Contribution,
7
7
  ContributionChangeEvent,
8
8
  contributionRegistry,
9
+ getContributionDisabled,
9
10
  HTMLContribution,
11
+ getContributionVisible,
10
12
  TOPIC_CONTRIBUTEIONS_CHANGED
11
13
  } from "../core/contributionregistry";
12
- import {Signal} from '@lit-labs/signals';
13
14
  import {unsafeHTML} from "lit/directives/unsafe-html.js";
14
15
  import {subscribe} from "../core/events";
15
16
  import {createRef, ref} from "lit/directives/ref.js";
@@ -217,7 +218,10 @@ export class DocksContextMenu extends DocksElement {
217
218
  private renderContribution(contribution: Contribution) {
218
219
  if ("command" in contribution) {
219
220
  const commandContribution = contribution as CommandContribution;
220
- const disabled = (commandContribution.disabled as Signal.Computed<boolean>)?.get();
221
+ if (!getContributionVisible(commandContribution)) {
222
+ return nothing;
223
+ }
224
+ const disabled = getContributionDisabled(commandContribution);
221
225
  return html`
222
226
  <docks-command
223
227
  cmd="${commandContribution.command}"
@@ -7,10 +7,11 @@ import {
7
7
  Contribution,
8
8
  ContributionChangeEvent,
9
9
  contributionRegistry,
10
+ getContributionDisabled,
10
11
  HTMLContribution,
12
+ getContributionVisible,
11
13
  TOPIC_CONTRIBUTEIONS_CHANGED
12
14
  } from "../core/contributionregistry";
13
- import {Signal} from '@lit-labs/signals';
14
15
  import {unsafeHTML} from "lit/directives/unsafe-html.js";
15
16
  import {subscribe} from "../core/events";
16
17
  import { icon } from "../core/icon-utils";
@@ -28,7 +29,8 @@ function renderButtonGroup(
28
29
  ) {
29
30
  const slot = slotName ?? 'default';
30
31
  const label = `Toolbar ${slot}`;
31
- const items = contributions.filter(c => c.slot === slotName && isToolbarItem(c));
32
+ const items = contributions.filter(c =>
33
+ c.slot === slotName && isToolbarItem(c) && getContributionVisible(c));
32
34
  const slotHtml = slotName === 'start'
33
35
  ? html`<slot name="start"></slot>`
34
36
  : slotName === 'end'
@@ -211,7 +213,7 @@ export class DocksToolbar extends DocksElement {
211
213
  return html`
212
214
  <wa-button @click=${() => void this.executeCommand(commandContribution.command, commandContribution.params || {})}
213
215
  title=${commandContribution.label}
214
- ?disabled="${(commandContribution.disabled as Signal.Computed<boolean>)?.get()}"
216
+ ?disabled="${getContributionDisabled(commandContribution)}"
215
217
  appearance="plain" size=${this.size}>
216
218
  ${icon(commandContribution.icon, { label: commandContribution.label })}
217
219
  ${showLabel ? commandContribution.label : ''}