@dolusoft/command-console 1.0.0 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/command-console.js
CHANGED
|
@@ -3,14 +3,15 @@ import { completionFeature, consoleFeatures, contextFeature, createConsole, reca
|
|
|
3
3
|
// src/dom/focus.ts
|
|
4
4
|
var interactive = "a, button, input, select, textarea, [tabindex], [contenteditable]";
|
|
5
5
|
/**
|
|
6
|
-
* Whether a click on `target` inside `
|
|
7
|
-
* text is selected, not inside an interactive element
|
|
6
|
+
* Whether a click on `target` inside `root` should focus the input: not when
|
|
7
|
+
* text is selected, not inside an interactive element (the input included:
|
|
8
|
+
* a click on it is the browser's).
|
|
8
9
|
*/
|
|
9
|
-
var clickFocusesInput = (target,
|
|
10
|
-
const selection =
|
|
10
|
+
var clickFocusesInput = (target, root) => {
|
|
11
|
+
const selection = root.ownerDocument.getSelection();
|
|
11
12
|
if (selection && !selection.isCollapsed && selection.toString() !== "") return false;
|
|
12
13
|
const found = (target instanceof Element ? target : target instanceof Node ? target.parentElement : null)?.closest(interactive);
|
|
13
|
-
return !(found &&
|
|
14
|
+
return !(found && root.contains(found));
|
|
14
15
|
};
|
|
15
16
|
// src/dom/selection.ts
|
|
16
17
|
/** Whether the document has a non-empty selection inside `root`. */
|
|
@@ -410,8 +411,8 @@ var command_console_default = /* @__PURE__ */ defineComponent({
|
|
|
410
411
|
onMounted(() => {
|
|
411
412
|
if (props.autofocus) focusInput();
|
|
412
413
|
});
|
|
413
|
-
const
|
|
414
|
-
if (
|
|
414
|
+
const onRootClick = (event) => {
|
|
415
|
+
if (rootElement.value && clickFocusesInput(event.target, rootElement.value)) focusInput();
|
|
415
416
|
};
|
|
416
417
|
let hadFocus = false;
|
|
417
418
|
watch(() => state.value.input.revision, () => {
|
|
@@ -513,14 +514,14 @@ var command_console_default = /* @__PURE__ */ defineComponent({
|
|
|
513
514
|
ref: "root",
|
|
514
515
|
class: "cc-console",
|
|
515
516
|
"data-busy": running.value ? "" : void 0,
|
|
516
|
-
"data-context-depth": unref(state).context.length
|
|
517
|
+
"data-context-depth": unref(state).context.length,
|
|
518
|
+
onClick: onRootClick
|
|
517
519
|
}, [
|
|
518
520
|
createElementVNode("div", {
|
|
519
521
|
ref: "log",
|
|
520
522
|
class: "cc-log",
|
|
521
523
|
role: "log",
|
|
522
524
|
"aria-label": labels.value.log,
|
|
523
|
-
onClick: onLogClick,
|
|
524
525
|
onScroll: _cache[0] || (_cache[0] = (...args) => unref(scroll).onScroll && unref(scroll).onScroll(...args))
|
|
525
526
|
}, [createElementVNode("div", _hoisted_3, [(openBlock(), createBlock(resolveDynamicComponent(entriesView)))], 512)], 40, _hoisted_2),
|
|
526
527
|
createElementVNode("div", _hoisted_4, [running.value ? renderSlot(_ctx.$slots, "busy", { running: running.value }, () => [createTextVNode(toDisplayString(labels.value.busy), 1)], void 0, 0) : (openBlock(), createElementBlock(Fragment, { key: 1 }, [createTextVNode(toDisplayString(recalled.value), 1)], 64))]),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"command-console.js","names":["_useSlots","_openBlock","_createElementBlock","_unref","_createElementVNode","_createBlock","_resolveDynamicComponent","_renderSlot","_createTextVNode","_toDisplayString","_Fragment","_sfc_main","CommandConsole"],"sources":["../src/dom/focus.ts","../src/dom/selection.ts","../src/dom/keys.ts","../src/dom/scroll.ts","../src/labels.ts","../src/parts/completions-view.ts","../src/parts/block-view.ts","../src/parts/entry-view.ts","../src/use-command-console.ts","../src/command-console.vue","../src/command-console.vue","../src/index.ts"],"sourcesContent":["// A click in the log focuses the input (README: DOM and styling, B-33), unless\n// the user is selecting text or clicked something of its own.\n\nconst interactive =\n 'a, button, input, select, textarea, [tabindex], [contenteditable]'\n\n/**\n * Whether a click on `target` inside `log` should focus the input: not when\n * text is selected, not inside an interactive element.\n */\nexport const clickFocusesInput = (\n target: EventTarget | null,\n log: Element\n): boolean => {\n const selection = log.ownerDocument.getSelection()\n if (selection && !selection.isCollapsed && selection.toString() !== '') {\n return false\n }\n const element =\n target instanceof Element\n ? target\n : target instanceof Node\n ? target.parentElement\n : null\n const found = element?.closest(interactive)\n return !(found && log.contains(found))\n}\n","// Copy is never taken over (README: Keyboard, D-07): with text selected in\n// the input, or in the console's part of the document, Ctrl+C stays the\n// browser's.\nimport type { KeyInput } from '@dolusoft/command-console-core'\n\n/** Whether the document has a non-empty selection inside `root`. */\nconst hasSelectionIn = (root: Element | null): boolean => {\n const selection = root?.ownerDocument.getSelection()\n if (!root || !selection || selection.isCollapsed) {\n return false\n }\n if (selection.toString() === '') {\n return false\n }\n const { anchorNode, focusNode } = selection\n return (\n (anchorNode !== null && root.contains(anchorNode)) ||\n (focusNode !== null && root.contains(focusNode))\n )\n}\n\n/** Where text is selected, as `KeyInput.selection` reports it. */\nexport const selectionOf = (\n input: HTMLInputElement,\n root: Element | null\n): KeyInput['selection'] => {\n if (\n input.selectionStart !== null &&\n input.selectionStart !== input.selectionEnd\n ) {\n return 'input'\n }\n return hasSelectionIn(root) ? 'document' : 'none'\n}\n","// KeyboardEvent → KeyInput (core README: State): the adapter's whole share of\n// the key map. Core never sees a DOM event.\nimport type { KeyInput } from '@dolusoft/command-console-core'\n\nimport { selectionOf } from './selection'\n\n/** The key press `event` on `input`, inside the console `root`. */\nexport const keyInputOf = (\n event: KeyboardEvent,\n input: HTMLInputElement,\n root: Element | null\n): KeyInput => ({\n key: event.key,\n ctrlKey: event.ctrlKey,\n shiftKey: event.shiftKey,\n altKey: event.altKey,\n metaKey: event.metaKey,\n // 229: the key an IME holds while it composes (older engines do not set\n // `isComposing` on the keydown that starts a composition).\n isComposing: event.isComposing || event.keyCode === 229,\n selection: selectionOf(input, root)\n})\n","// Scroll (README: DOM and styling, Q-08, C-58): the log sticks to the bottom\n// while the user is within 2 px of it, and after the user's own submit; new\n// output does not move a view the user scrolled up. `scrollTop` is the only\n// style-related write of the package (P5).\nimport { nextTick, onBeforeUnmount, onMounted, type ShallowRef } from 'vue'\n\n/** How close to the bottom still counts as at the bottom, in px. */\nconst slack = 2\n\nexport interface StickToBottom {\n /** The user's own submit: stick again and show the bottom now. */\n stick(): void\n /** The log's `scroll` event. */\n onScroll(): void\n}\n\nexport const useStickToBottom = (\n log: Readonly<ShallowRef<HTMLElement | null>>,\n entries: Readonly<ShallowRef<HTMLElement | null>>\n): StickToBottom => {\n let stuck = true\n let observer: ResizeObserver | null = null\n let mounted = false\n\n const toBottom = () => {\n const element = log.value\n if (mounted && element) {\n element.scrollTop = element.scrollHeight\n }\n }\n\n onMounted(() => {\n mounted = true\n // One observer on the entries; a late callback after unmount does\n // nothing (D-04).\n if (typeof ResizeObserver !== 'undefined' && entries.value) {\n observer = new ResizeObserver(() => {\n if (stuck) {\n toBottom()\n }\n })\n observer.observe(entries.value)\n }\n })\n\n onBeforeUnmount(() => {\n mounted = false\n observer?.disconnect()\n observer = null\n })\n\n return {\n stick() {\n stuck = true\n void nextTick(toBottom)\n },\n onScroll() {\n const element = log.value\n if (element) {\n stuck =\n element.scrollHeight - element.scrollTop - element.clientHeight <=\n slack\n }\n }\n }\n}\n","// Every text the component renders comes from here (P7, C-47): English\n// defaults, merged one level deep with the `labels` prop.\nimport type { MessageBlock, MessageParams } from '@dolusoft/command-protocol'\n\nimport type { ConsoleLabels, ConsoleLabelsInput } from './contract'\n\n/** The English defaults (README: Labels). */\nconst defaultLabels: ConsoleLabels = {\n input: 'Command',\n inputDescription:\n 'Enter runs the command. Tab completes; on an empty line it leaves the console.',\n log: 'Console output',\n redacted: '(input hidden)',\n cancelledMarker: '^C',\n completions: 'Suggestions',\n busy: 'Running…',\n messages: {\n 'command-not-found': params =>\n params.name === null\n ? 'command not found'\n : `${params.name}: command not found`,\n 'command-failed': 'The command failed.',\n 'unterminated-quote': 'Unterminated quote.',\n interrupted: 'Interrupted.'\n }\n}\n\n/**\n * The defaults with `labels` over them; `messages` merges one level deep, so\n * overriding one message keeps the others.\n */\nexport const resolveLabels = (labels?: ConsoleLabelsInput): ConsoleLabels => ({\n ...defaultLabels,\n ...labels,\n messages: { ...defaultLabels.messages, ...labels?.messages }\n})\n\n/** The text of a message block, from `labels.messages`. */\nexport const messageText = (\n labels: ConsoleLabels,\n block: MessageBlock\n): string => {\n const label = labels.messages[block.code] as\n string | ((params: MessageParams[typeof block.code]) => string)\n if (typeof label === 'string') {\n return label\n }\n // A library-made block of a code with parameters always carries them;\n // `{}` keeps a consumer-made block without them from throwing.\n return label((block.params ?? {}) as never)\n}\n","// The completion list (README: DOM and styling, Completion): a named listbox,\n// one option per candidate, rendered only while a session is open. The ids tie\n// it to the input's `aria-controls` and `aria-activedescendant` (C-48).\nimport { h, type VNode, type VNodeArrayChildren } from 'vue'\n\nimport type { CompletionSession } from '@dolusoft/command-console-core'\n\nimport type { ConsoleSlots } from '../contract'\n\n/** The id of candidate `index` of the list `listId`. */\nexport const optionId = (listId: string, index: number): string =>\n `${listId}-${index}`\n\nexport const renderCompletions = (\n session: CompletionSession,\n listId: string,\n label: string,\n slots: Pick<ConsoleSlots, 'completion'>\n): VNode =>\n h(\n 'ul',\n {\n class: 'cc-completions',\n role: 'listbox',\n id: listId,\n 'aria-label': label\n },\n session.candidates.map((candidate, index) => {\n const active = index === session.active\n return h(\n 'li',\n {\n key: `${index}:${candidate}`,\n class: 'cc-completion',\n role: 'option',\n id: optionId(listId, index),\n 'aria-selected': active ? 'true' : 'false'\n },\n slots.completion\n ? (slots.completion({\n candidate,\n index,\n active\n }) as VNodeArrayChildren)\n : candidate\n )\n })\n )\n","// One output block (README: DOM and styling): `.cc-block` with its `data-type`,\n// in the element of its type, or a `div` around the consumer's `block-<type>`\n// slot (C-67). All text goes through Vue's escaping; there is no HTML (S-08).\nimport { h, type VNode, type VNodeArrayChildren } from 'vue'\n\nimport type {\n JsonPrimitive,\n OutputBlock,\n OutputEntry\n} from '@dolusoft/command-protocol'\n\nimport type { BlockSlotProps, ConsoleLabels, ConsoleSlots } from '../contract'\nimport { messageText } from '../labels'\n\n/** The `block-<type>` slots the component was given. */\nexport type BlockSlots = Pick<\n ConsoleSlots,\n | 'block-text'\n | 'block-json'\n | 'block-table'\n | 'block-list'\n | 'block-message'\n | 'block-custom'\n>\n\nconst json = (value: unknown) => JSON.stringify(value, null, 2)\n\n/** A table cell: a missing key and `null` are empty. */\nconst cellText = (value: JsonPrimitive | undefined): string =>\n value === undefined || value === null ? '' : String(value)\n\nconst content = (block: OutputBlock, labels: ConsoleLabels): VNode => {\n const hooks = { class: 'cc-block', 'data-type': block.type }\n switch (block.type) {\n case 'text':\n return h('pre', hooks, block.text)\n case 'json':\n return h('pre', hooks, json(block.value))\n case 'table':\n return h('table', hooks, [\n h(\n 'thead',\n h(\n 'tr',\n block.columns.map(column => h('th', { scope: 'col' }, column.label))\n )\n ),\n h(\n 'tbody',\n block.rows.map(row =>\n h(\n 'tr',\n block.columns.map(column => h('td', cellText(row[column.key])))\n )\n )\n )\n ])\n case 'list':\n return h(\n 'ul',\n hooks,\n block.items.map(item => h('li', item))\n )\n case 'message':\n return h('div', hooks, messageText(labels, block))\n case 'custom':\n return h('pre', hooks, json(block))\n }\n}\n\n/** Renders `block` of `entry`, through its slot when one is given. */\nexport const renderBlock = (\n block: OutputBlock,\n entry: OutputEntry,\n labels: ConsoleLabels,\n slots: BlockSlots\n): VNode => {\n const slot = slots[`block-${block.type}`] as\n ((props: BlockSlotProps) => unknown) | undefined\n if (slot) {\n return h(\n 'div',\n { class: 'cc-block', 'data-type': block.type },\n slot({ block, entry }) as VNodeArrayChildren\n )\n }\n return content(block, labels)\n}\n","// One entry of the log (README: DOM and styling): an ended input line (its\n// prompt, its text or `labels.redacted`, the cancelled marker) or the blocks of\n// an output.\nimport { h, type VNode, type VNodeArrayChildren } from 'vue'\n\nimport type { ConsoleEntry } from '@dolusoft/command-protocol'\n\nimport { renderBlock, type BlockSlots } from './block-view'\nimport type { ConsoleLabels, ConsoleSlots } from '../contract'\n\n/** The slots an entry may use. */\nexport type EntrySlots = BlockSlots & Pick<ConsoleSlots, 'prompt'>\n\n/** `span.cc-prompt` with the `prompt` slot inside, or the prompt text. */\nexport const renderPrompt = (\n prompt: string,\n slots: Pick<ConsoleSlots, 'prompt'>\n): VNode =>\n h(\n 'span',\n { class: 'cc-prompt' },\n slots.prompt ? (slots.prompt({ prompt }) as VNodeArrayChildren) : prompt\n )\n\nexport const renderEntry = (\n entry: ConsoleEntry,\n labels: ConsoleLabels,\n slots: EntrySlots\n): VNode => {\n if (entry.kind === 'input') {\n return h(\n 'div',\n {\n key: entry.id,\n class: 'cc-entry',\n 'data-kind': 'input',\n 'data-status': entry.status,\n 'data-redacted': entry.redacted ? '' : undefined\n },\n [\n renderPrompt(entry.prompt, slots),\n h(\n 'span',\n { class: 'cc-line' },\n entry.redacted || entry.text === null ? labels.redacted : entry.text\n ),\n entry.status === 'cancelled'\n ? h('span', { class: 'cc-cancelled' }, labels.cancelledMarker)\n : null\n ]\n )\n }\n return h(\n 'div',\n {\n key: entry.id,\n class: 'cc-entry',\n 'data-kind': 'output',\n 'data-tone': entry.tone,\n 'aria-live': entry.quiet ? 'off' : undefined\n },\n entry.blocks.map(block => renderBlock(block, entry, labels, slots))\n )\n}\n","// useCommandConsole (README: The composable): a console without markup. It\n// creates the console once, passes reactive option changes to `setOptions`,\n// mirrors every notification into a `shallowRef` and disposes the console with\n// its effect scope. It does no DOM work, so a consumer can draw a template of\n// its own. The Vue package keeps no console state of its own (P15).\nimport {\n onScopeDispose,\n shallowReadonly,\n shallowRef,\n toValue,\n unref,\n watch\n} from 'vue'\n\nimport {\n createConsole,\n type ConsoleOptions,\n type ConsoleState,\n type OptionalFeatures\n} from '@dolusoft/command-console-core'\n\nimport type {\n UseCommandConsoleOptions,\n UseCommandConsoleReturn\n} from './contract'\n\n/** The options that are functions: a ref may hold one, a getter may not. */\nconst functionKeys = [\n 'onEntriesChange',\n 'onRecallChange',\n 'onContextChange',\n 'createId',\n 'beforeRun',\n 'onError',\n 'formatError',\n 'onClose',\n 'isSensitive',\n 'shouldRecord'\n] as const\n\n/** The options that are values: a ref or a getter may give them. */\nconst valueKeys = [\n 'commands',\n 'prompt',\n 'state',\n 'maxEntries',\n 'recallLimit'\n] as const\n\n/**\n * Creates a console from `options` and keeps it in step with them.\n *\n * A value option (`commands`, `prompt`, `state`, `maxEntries`,\n * `recallLimit`) may be a plain value, a ref or a getter. A function option\n * (`beforeRun`, `onError`, …) may be the function or a ref that holds it: a\n * bare function is the option itself, never a getter. `features` and\n * `initialState` are read once.\n *\n * @example\n * ```ts\n * const { console, state } = useCommandConsole({\n * features: consoleFeatures({ recallFeature }),\n * commands: () => commands.value\n * })\n * ```\n */\nexport function useCommandConsole<\n // `{}` and not its constraint, as `createConsole` (ADR 0002).\n // eslint-disable-next-line @typescript-eslint/no-empty-object-type\n T extends Partial<OptionalFeatures> = {}\n>(options: UseCommandConsoleOptions<T>): UseCommandConsoleReturn<T> {\n const resolve = (): ConsoleOptions<T> => {\n const resolved: Record<string, unknown> = {}\n for (const key of valueKeys) {\n resolved[key] = toValue(options[key])\n }\n for (const key of functionKeys) {\n resolved[key] = unref(options[key])\n }\n return resolved as unknown as ConsoleOptions<T>\n }\n\n const console = createConsole<T>({\n ...resolve(),\n features: options.features,\n initialState: toValue(options.initialState)\n })\n\n const state = shallowRef<ConsoleState>(console.getState())\n const unsubscribe = console.subscribe(next => {\n state.value = next\n })\n\n // Every reactive read of `resolve` is a dependency: a change of any option\n // hands the whole set to the console. The features stay those it was\n // created with.\n const stop = watch(resolve, next => {\n console.setOptions(old => ({\n ...next,\n features: old.features,\n initialState: old.initialState\n }))\n })\n\n onScopeDispose(() => {\n stop()\n unsubscribe()\n console.dispose()\n // The snapshot goes with the console: what it held (the live input\n // among it) is not kept.\n state.value = console.getState()\n })\n\n return {\n console: console,\n state: shallowReadonly(state)\n }\n}\n","<script setup lang=\"ts\">\n// <CommandConsole> (README: The component): the composable's console drawn as\n// the DOM of `contract/dom.ts` (README: DOM and styling). The component holds\n// no console state (P15): it renders the snapshot and forwards DOM events to\n// core actions. What it keeps is DOM-bound: element refs, the \"stuck to\n// bottom\" flag, the last value it emitted per controlled slice and the caret\n// it last reported.\nimport {\n computed,\n onMounted,\n toRef,\n useId,\n useTemplateRef,\n watch,\n type FunctionalComponent\n} from 'vue'\n\nimport {\n completionFeature,\n consoleFeatures,\n contextFeature,\n recallFeature,\n type OptionalFeatures,\n type Updater\n} from '@dolusoft/command-console-core'\nimport type { ConsoleEntry, ContextFrame } from '@dolusoft/command-protocol'\n\nimport type {\n ConsoleEmits,\n ConsoleProps,\n ConsoleSlots,\n ConsoleSubmit\n} from './contract'\nimport { clickFocusesInput } from './dom/focus'\nimport { keyInputOf } from './dom/keys'\nimport { useStickToBottom } from './dom/scroll'\nimport { resolveLabels } from './labels'\nimport { renderCompletions, optionId } from './parts/completions-view'\nimport { renderEntry, renderPrompt } from './parts/entry-view'\nimport { useCommandConsole } from './use-command-console'\n\ndefineOptions({ name: 'CommandConsole' })\n\nconst props = withDefaults(defineProps<ConsoleProps>(), {\n prompt: undefined,\n initialState: undefined,\n createId: undefined,\n maxEntries: undefined,\n beforeRun: undefined,\n onError: undefined,\n formatError: undefined,\n isSensitive: undefined,\n recallLimit: undefined,\n shouldRecord: undefined,\n labels: undefined,\n autofocus: false,\n entries: undefined,\n recall: undefined,\n context: undefined\n})\n\nconst emit = defineEmits<ConsoleEmits>()\n\nconst slots = defineSlots<ConsoleSlots>()\n\n// The component always uses every optional feature (README: The component).\nconst features: OptionalFeatures = consoleFeatures({\n recallFeature,\n completionFeature,\n contextFeature\n})\n\n// --- v-model (core README: Controlled slices, C-61) -------------------------\n// An updater is resolved against the value this component last emitted for\n// the slice, else the prop: a second action in the same tick builds on the\n// first one's value, not on a prop the parent has not re-rendered yet. A new\n// prop value clears it.\ntype Slice = 'entries' | 'recall' | 'context'\ninterface SliceValues {\n entries: ConsoleEntry[]\n recall: string[]\n context: ContextFrame[]\n}\nconst lastEmitted: Partial<SliceValues> = {}\nfor (const slice of ['entries', 'recall', 'context'] as const) {\n watch(\n () => props[slice],\n () => {\n delete lastEmitted[slice]\n },\n { flush: 'sync' }\n )\n}\nconst report =\n <K extends Slice>(slice: K, send: (value: SliceValues[K]) => void) =>\n (updater: Updater<SliceValues[K]>) => {\n const base = (lastEmitted[slice] ?? props[slice] ?? []) as SliceValues[K]\n const value = typeof updater === 'function' ? updater(base) : updater\n lastEmitted[slice] = value\n send(value)\n }\n\n/** The slices bound with `v-model`; the others are the console's. */\nconst controlled = () => {\n const state: Partial<SliceValues> = {}\n if (props.entries !== undefined) {\n state.entries = props.entries\n }\n if (props.recall !== undefined) {\n state.recall = props.recall\n }\n if (props.context !== undefined) {\n state.context = props.context\n }\n return state\n}\n\nconst { console, state } = useCommandConsole({\n features,\n commands: () => props.commands,\n prompt: () => props.prompt,\n state: controlled,\n maxEntries: () => props.maxEntries,\n recallLimit: () => props.recallLimit,\n initialState: props.initialState,\n createId: toRef(props, 'createId'),\n beforeRun: toRef(props, 'beforeRun'),\n onError: toRef(props, 'onError'),\n formatError: toRef(props, 'formatError'),\n isSensitive: toRef(props, 'isSensitive'),\n shouldRecord: toRef(props, 'shouldRecord'),\n onEntriesChange: report('entries', value => emit('update:entries', value)),\n onRecallChange: report('recall', value => emit('update:recall', value)),\n onContextChange: report('context', value => emit('update:context', value)),\n onClose: () => emit('close')\n})\n\n// --- What the template draws ------------------------------------------------\n\nconst labels = computed(() => resolveLabels(props.labels))\nconst listId = useId()\n\nconst running = computed(() => state.value.running)\nconst session = computed(() => {\n const completion = state.value.completion\n return completion?.status === 'open' ? completion : null\n})\nconst activeId = computed(() =>\n session.value && session.value.active >= 0\n ? optionId(listId, session.value.active)\n : undefined\n)\n/** The prompt of the live line: the top context frame's, or the root's. */\nconst prompt = computed(() => {\n void state.value.context\n void props.prompt\n return console.getPrompt()\n})\n/**\n * The recalled line, for the status element (README: Recall): a screen reader\n * does not announce a scripted value change in a focused field.\n */\nconst recalled = computed(() => {\n const { recall, recallCursor } = state.value\n return recallCursor !== null ? (recall[recallCursor] ?? '') : ''\n})\n\nconst entriesView: FunctionalComponent = () =>\n state.value.entries.map(entry => renderEntry(entry, labels.value, slots))\nconst completionsView: FunctionalComponent = () =>\n session.value\n ? renderCompletions(session.value, listId, labels.value.completions, slots)\n : null\nconst promptView: FunctionalComponent = () => renderPrompt(prompt.value, slots)\n\n// --- Elements ---------------------------------------------------------------\n\nconst rootElement = useTemplateRef<HTMLDivElement>('root')\nconst logElement = useTemplateRef<HTMLDivElement>('log')\nconst entriesElement = useTemplateRef<HTMLDivElement>('entries')\nconst inputElement = useTemplateRef<HTMLInputElement>('input')\n\nconst scroll = useStickToBottom(logElement, entriesElement)\n\nconst focusInput = () => inputElement.value?.focus()\n\nonMounted(() => {\n if (props.autofocus) {\n focusInput()\n }\n})\n\n// C-51: a click in the log focuses the input, unless text is selected or\n// the click was on something interactive.\nconst onLogClick = (event: MouseEvent) => {\n if (logElement.value && clickFocusesInput(event.target, logElement.value)) {\n focusInput()\n }\n}\n\n// --- The input element (README: DOM and styling, C-54) ----------------------\n// The element is keyed by `input.revision`. When it is replaced, the new\n// one gets focus only if the old one had it, and the caret goes to the end.\n// Its value is written as a DOM property here, never bound in the template:\n// Vue mirrors a bound `value` into the `value` attribute, which would put\n// the typed line into the DOM.\n\nlet hadFocus = false\nwatch(\n () => state.value.input.revision,\n () => {\n const element = inputElement.value\n hadFocus =\n element !== null && element.ownerDocument.activeElement === element\n // The element about to go may hold a sensitive line (C-36).\n if (element) {\n element.value = ''\n }\n },\n { flush: 'pre' }\n)\n\n/** The value and caret this component last reported (a user edit). */\nlet reported = { value: '', caret: 0 }\n\n/**\n * Writes the console's value into the element. A write by the console\n * (completion, recall) also sets the caret it chose; the user's own edit is\n * left alone, so a selection the user made stays.\n */\nconst syncElement = (replaced: boolean) => {\n const element = inputElement.value\n if (!element) {\n return\n }\n const { value, caret } = state.value.input\n if (element.value !== value) {\n element.value = value\n }\n if (replaced) {\n if (hadFocus) {\n element.focus()\n }\n hadFocus = false\n element.setSelectionRange(value.length, value.length)\n return\n }\n if (\n element.ownerDocument.activeElement !== element ||\n (value === reported.value && caret === reported.caret)\n ) {\n return\n }\n if (element.selectionStart !== caret || element.selectionEnd !== caret) {\n element.setSelectionRange(caret, caret)\n }\n}\nonMounted(() => {\n syncElement(false)\n})\nwatch(\n () => state.value.input,\n (next, previous) => {\n syncElement(next.revision !== previous.revision)\n },\n { flush: 'post' }\n)\n\n/** Reports the element's value and caret to the console (a user edit). */\nconst reportEdit = (element: HTMLInputElement) => {\n const caret = element.selectionStart ?? element.value.length\n reported = { value: element.value, caret }\n console.editInput(element.value, caret)\n}\n\nconst onInput = (event: Event) => {\n reportEdit(event.currentTarget as HTMLInputElement)\n}\n\n// A caret move the user made (click, arrow keys, selection).\nconst onCaret = (event: Event) => {\n const element = event.currentTarget as HTMLInputElement\n const caret = element.selectionStart ?? element.value.length\n if (element.value !== state.value.input.value) {\n return\n }\n if (caret !== state.value.input.caret) {\n reportEdit(element)\n }\n}\n\n// --- Submits (C-64) ---------------------------------------------------------\n\n/**\n * Runs `action` and emits `submit` for the input entry it added, if any;\n * keeps the log at the bottom after the user's own submit (C-58).\n */\nconst submitting = <T,>(action: () => T): T => {\n const before = new Set(state.value.entries.map(entry => entry.id))\n const result = action()\n const added = console\n .getState()\n .entries.find(\n entry =>\n entry.kind === 'input' &&\n entry.status === 'submitted' &&\n !before.has(entry.id)\n )\n if (added) {\n scroll.stick()\n const submit: ConsoleSubmit = {\n inputId: added.id,\n sensitive: added.kind === 'input' && added.redacted\n }\n emit('submit', submit)\n }\n return result\n}\n\nconst onKeydown = (event: KeyboardEvent) => {\n const element = event.currentTarget as HTMLInputElement\n const key = keyInputOf(event, element, rootElement.value)\n if (!key.isComposing) {\n // The caret may have moved without an event the console saw.\n onCaret(event)\n }\n const handled = submitting(() => console.handleKey(key))\n if (handled) {\n event.preventDefault()\n }\n}\n\n// --- Expose ------------------------------------------------------------------\n\nconst exposed = {\n focus: () => {\n focusInput()\n },\n run: (line: string) => submitting(() => console.run(line)),\n cancel: () => {\n console.cancel()\n },\n reset: () => {\n console.reset()\n },\n clear: () => {\n console.clearEntries()\n },\n console,\n state\n}\ndefineExpose(exposed)\n</script>\n\n<template>\n <div\n ref=\"root\"\n class=\"cc-console\"\n :data-busy=\"running ? '' : undefined\"\n :data-context-depth=\"state.context.length\"\n >\n <div\n ref=\"log\"\n class=\"cc-log\"\n role=\"log\"\n :aria-label=\"labels.log\"\n @click=\"onLogClick\"\n @scroll=\"scroll.onScroll\"\n >\n <div ref=\"entries\" class=\"cc-entries\">\n <component :is=\"entriesView\" />\n </div>\n </div>\n <div class=\"cc-status\" role=\"status\">\n <template v-if=\"running\"\n ><slot name=\"busy\" :running=\"running\">{{ labels.busy }}</slot></template\n ><template v-else>{{ recalled }}</template>\n </div>\n <component :is=\"completionsView\" />\n <div class=\"cc-input-line\">\n <component :is=\"promptView\" />\n <input\n :key=\"state.input.revision\"\n ref=\"input\"\n class=\"cc-input\"\n type=\"text\"\n role=\"combobox\"\n aria-autocomplete=\"list\"\n :aria-expanded=\"session ? 'true' : 'false'\"\n :aria-controls=\"session ? listId : undefined\"\n :aria-activedescendant=\"activeId\"\n :aria-label=\"labels.input\"\n :aria-description=\"labels.inputDescription\"\n autocomplete=\"off\"\n autocorrect=\"off\"\n autocapitalize=\"off\"\n spellcheck=\"false\"\n :readonly=\"running !== null\"\n @input=\"onInput\"\n @keydown=\"onKeydown\"\n @keyup=\"onCaret\"\n @click=\"onCaret\"\n @selectionchange=\"onCaret\"\n />\n </div>\n </div>\n</template>\n","<script setup lang=\"ts\">\n// <CommandConsole> (README: The component): the composable's console drawn as\n// the DOM of `contract/dom.ts` (README: DOM and styling). The component holds\n// no console state (P15): it renders the snapshot and forwards DOM events to\n// core actions. What it keeps is DOM-bound: element refs, the \"stuck to\n// bottom\" flag, the last value it emitted per controlled slice and the caret\n// it last reported.\nimport {\n computed,\n onMounted,\n toRef,\n useId,\n useTemplateRef,\n watch,\n type FunctionalComponent\n} from 'vue'\n\nimport {\n completionFeature,\n consoleFeatures,\n contextFeature,\n recallFeature,\n type OptionalFeatures,\n type Updater\n} from '@dolusoft/command-console-core'\nimport type { ConsoleEntry, ContextFrame } from '@dolusoft/command-protocol'\n\nimport type {\n ConsoleEmits,\n ConsoleProps,\n ConsoleSlots,\n ConsoleSubmit\n} from './contract'\nimport { clickFocusesInput } from './dom/focus'\nimport { keyInputOf } from './dom/keys'\nimport { useStickToBottom } from './dom/scroll'\nimport { resolveLabels } from './labels'\nimport { renderCompletions, optionId } from './parts/completions-view'\nimport { renderEntry, renderPrompt } from './parts/entry-view'\nimport { useCommandConsole } from './use-command-console'\n\ndefineOptions({ name: 'CommandConsole' })\n\nconst props = withDefaults(defineProps<ConsoleProps>(), {\n prompt: undefined,\n initialState: undefined,\n createId: undefined,\n maxEntries: undefined,\n beforeRun: undefined,\n onError: undefined,\n formatError: undefined,\n isSensitive: undefined,\n recallLimit: undefined,\n shouldRecord: undefined,\n labels: undefined,\n autofocus: false,\n entries: undefined,\n recall: undefined,\n context: undefined\n})\n\nconst emit = defineEmits<ConsoleEmits>()\n\nconst slots = defineSlots<ConsoleSlots>()\n\n// The component always uses every optional feature (README: The component).\nconst features: OptionalFeatures = consoleFeatures({\n recallFeature,\n completionFeature,\n contextFeature\n})\n\n// --- v-model (core README: Controlled slices, C-61) -------------------------\n// An updater is resolved against the value this component last emitted for\n// the slice, else the prop: a second action in the same tick builds on the\n// first one's value, not on a prop the parent has not re-rendered yet. A new\n// prop value clears it.\ntype Slice = 'entries' | 'recall' | 'context'\ninterface SliceValues {\n entries: ConsoleEntry[]\n recall: string[]\n context: ContextFrame[]\n}\nconst lastEmitted: Partial<SliceValues> = {}\nfor (const slice of ['entries', 'recall', 'context'] as const) {\n watch(\n () => props[slice],\n () => {\n delete lastEmitted[slice]\n },\n { flush: 'sync' }\n )\n}\nconst report =\n <K extends Slice>(slice: K, send: (value: SliceValues[K]) => void) =>\n (updater: Updater<SliceValues[K]>) => {\n const base = (lastEmitted[slice] ?? props[slice] ?? []) as SliceValues[K]\n const value = typeof updater === 'function' ? updater(base) : updater\n lastEmitted[slice] = value\n send(value)\n }\n\n/** The slices bound with `v-model`; the others are the console's. */\nconst controlled = () => {\n const state: Partial<SliceValues> = {}\n if (props.entries !== undefined) {\n state.entries = props.entries\n }\n if (props.recall !== undefined) {\n state.recall = props.recall\n }\n if (props.context !== undefined) {\n state.context = props.context\n }\n return state\n}\n\nconst { console, state } = useCommandConsole({\n features,\n commands: () => props.commands,\n prompt: () => props.prompt,\n state: controlled,\n maxEntries: () => props.maxEntries,\n recallLimit: () => props.recallLimit,\n initialState: props.initialState,\n createId: toRef(props, 'createId'),\n beforeRun: toRef(props, 'beforeRun'),\n onError: toRef(props, 'onError'),\n formatError: toRef(props, 'formatError'),\n isSensitive: toRef(props, 'isSensitive'),\n shouldRecord: toRef(props, 'shouldRecord'),\n onEntriesChange: report('entries', value => emit('update:entries', value)),\n onRecallChange: report('recall', value => emit('update:recall', value)),\n onContextChange: report('context', value => emit('update:context', value)),\n onClose: () => emit('close')\n})\n\n// --- What the template draws ------------------------------------------------\n\nconst labels = computed(() => resolveLabels(props.labels))\nconst listId = useId()\n\nconst running = computed(() => state.value.running)\nconst session = computed(() => {\n const completion = state.value.completion\n return completion?.status === 'open' ? completion : null\n})\nconst activeId = computed(() =>\n session.value && session.value.active >= 0\n ? optionId(listId, session.value.active)\n : undefined\n)\n/** The prompt of the live line: the top context frame's, or the root's. */\nconst prompt = computed(() => {\n void state.value.context\n void props.prompt\n return console.getPrompt()\n})\n/**\n * The recalled line, for the status element (README: Recall): a screen reader\n * does not announce a scripted value change in a focused field.\n */\nconst recalled = computed(() => {\n const { recall, recallCursor } = state.value\n return recallCursor !== null ? (recall[recallCursor] ?? '') : ''\n})\n\nconst entriesView: FunctionalComponent = () =>\n state.value.entries.map(entry => renderEntry(entry, labels.value, slots))\nconst completionsView: FunctionalComponent = () =>\n session.value\n ? renderCompletions(session.value, listId, labels.value.completions, slots)\n : null\nconst promptView: FunctionalComponent = () => renderPrompt(prompt.value, slots)\n\n// --- Elements ---------------------------------------------------------------\n\nconst rootElement = useTemplateRef<HTMLDivElement>('root')\nconst logElement = useTemplateRef<HTMLDivElement>('log')\nconst entriesElement = useTemplateRef<HTMLDivElement>('entries')\nconst inputElement = useTemplateRef<HTMLInputElement>('input')\n\nconst scroll = useStickToBottom(logElement, entriesElement)\n\nconst focusInput = () => inputElement.value?.focus()\n\nonMounted(() => {\n if (props.autofocus) {\n focusInput()\n }\n})\n\n// C-51: a click in the log focuses the input, unless text is selected or\n// the click was on something interactive.\nconst onLogClick = (event: MouseEvent) => {\n if (logElement.value && clickFocusesInput(event.target, logElement.value)) {\n focusInput()\n }\n}\n\n// --- The input element (README: DOM and styling, C-54) ----------------------\n// The element is keyed by `input.revision`. When it is replaced, the new\n// one gets focus only if the old one had it, and the caret goes to the end.\n// Its value is written as a DOM property here, never bound in the template:\n// Vue mirrors a bound `value` into the `value` attribute, which would put\n// the typed line into the DOM.\n\nlet hadFocus = false\nwatch(\n () => state.value.input.revision,\n () => {\n const element = inputElement.value\n hadFocus =\n element !== null && element.ownerDocument.activeElement === element\n // The element about to go may hold a sensitive line (C-36).\n if (element) {\n element.value = ''\n }\n },\n { flush: 'pre' }\n)\n\n/** The value and caret this component last reported (a user edit). */\nlet reported = { value: '', caret: 0 }\n\n/**\n * Writes the console's value into the element. A write by the console\n * (completion, recall) also sets the caret it chose; the user's own edit is\n * left alone, so a selection the user made stays.\n */\nconst syncElement = (replaced: boolean) => {\n const element = inputElement.value\n if (!element) {\n return\n }\n const { value, caret } = state.value.input\n if (element.value !== value) {\n element.value = value\n }\n if (replaced) {\n if (hadFocus) {\n element.focus()\n }\n hadFocus = false\n element.setSelectionRange(value.length, value.length)\n return\n }\n if (\n element.ownerDocument.activeElement !== element ||\n (value === reported.value && caret === reported.caret)\n ) {\n return\n }\n if (element.selectionStart !== caret || element.selectionEnd !== caret) {\n element.setSelectionRange(caret, caret)\n }\n}\nonMounted(() => {\n syncElement(false)\n})\nwatch(\n () => state.value.input,\n (next, previous) => {\n syncElement(next.revision !== previous.revision)\n },\n { flush: 'post' }\n)\n\n/** Reports the element's value and caret to the console (a user edit). */\nconst reportEdit = (element: HTMLInputElement) => {\n const caret = element.selectionStart ?? element.value.length\n reported = { value: element.value, caret }\n console.editInput(element.value, caret)\n}\n\nconst onInput = (event: Event) => {\n reportEdit(event.currentTarget as HTMLInputElement)\n}\n\n// A caret move the user made (click, arrow keys, selection).\nconst onCaret = (event: Event) => {\n const element = event.currentTarget as HTMLInputElement\n const caret = element.selectionStart ?? element.value.length\n if (element.value !== state.value.input.value) {\n return\n }\n if (caret !== state.value.input.caret) {\n reportEdit(element)\n }\n}\n\n// --- Submits (C-64) ---------------------------------------------------------\n\n/**\n * Runs `action` and emits `submit` for the input entry it added, if any;\n * keeps the log at the bottom after the user's own submit (C-58).\n */\nconst submitting = <T,>(action: () => T): T => {\n const before = new Set(state.value.entries.map(entry => entry.id))\n const result = action()\n const added = console\n .getState()\n .entries.find(\n entry =>\n entry.kind === 'input' &&\n entry.status === 'submitted' &&\n !before.has(entry.id)\n )\n if (added) {\n scroll.stick()\n const submit: ConsoleSubmit = {\n inputId: added.id,\n sensitive: added.kind === 'input' && added.redacted\n }\n emit('submit', submit)\n }\n return result\n}\n\nconst onKeydown = (event: KeyboardEvent) => {\n const element = event.currentTarget as HTMLInputElement\n const key = keyInputOf(event, element, rootElement.value)\n if (!key.isComposing) {\n // The caret may have moved without an event the console saw.\n onCaret(event)\n }\n const handled = submitting(() => console.handleKey(key))\n if (handled) {\n event.preventDefault()\n }\n}\n\n// --- Expose ------------------------------------------------------------------\n\nconst exposed = {\n focus: () => {\n focusInput()\n },\n run: (line: string) => submitting(() => console.run(line)),\n cancel: () => {\n console.cancel()\n },\n reset: () => {\n console.reset()\n },\n clear: () => {\n console.clearEntries()\n },\n console,\n state\n}\ndefineExpose(exposed)\n</script>\n\n<template>\n <div\n ref=\"root\"\n class=\"cc-console\"\n :data-busy=\"running ? '' : undefined\"\n :data-context-depth=\"state.context.length\"\n >\n <div\n ref=\"log\"\n class=\"cc-log\"\n role=\"log\"\n :aria-label=\"labels.log\"\n @click=\"onLogClick\"\n @scroll=\"scroll.onScroll\"\n >\n <div ref=\"entries\" class=\"cc-entries\">\n <component :is=\"entriesView\" />\n </div>\n </div>\n <div class=\"cc-status\" role=\"status\">\n <template v-if=\"running\"\n ><slot name=\"busy\" :running=\"running\">{{ labels.busy }}</slot></template\n ><template v-else>{{ recalled }}</template>\n </div>\n <component :is=\"completionsView\" />\n <div class=\"cc-input-line\">\n <component :is=\"promptView\" />\n <input\n :key=\"state.input.revision\"\n ref=\"input\"\n class=\"cc-input\"\n type=\"text\"\n role=\"combobox\"\n aria-autocomplete=\"list\"\n :aria-expanded=\"session ? 'true' : 'false'\"\n :aria-controls=\"session ? listId : undefined\"\n :aria-activedescendant=\"activeId\"\n :aria-label=\"labels.input\"\n :aria-description=\"labels.inputDescription\"\n autocomplete=\"off\"\n autocorrect=\"off\"\n autocapitalize=\"off\"\n spellcheck=\"false\"\n :readonly=\"running !== null\"\n @input=\"onInput\"\n @keydown=\"onKeydown\"\n @keyup=\"onCaret\"\n @click=\"onCaret\"\n @selectionchange=\"onCaret\"\n />\n </div>\n </div>\n</template>\n","// @dolusoft/command-console: a headless command console for Vue 3.\n// `useCommandConsole` is the console without markup; `CommandConsole` draws\n// it as the DOM of `contract/dom.ts`. No CSS (P5).\nimport CommandConsole from './command-console.vue'\n\nexport { CommandConsole }\nexport default CommandConsole\n\nexport { useCommandConsole } from './use-command-console'\n\nexport type * from './contract'\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"command-console.js","names":["_useSlots","_openBlock","_createElementBlock","_unref","_createElementVNode","_createBlock","_resolveDynamicComponent","_renderSlot","_createTextVNode","_toDisplayString","_Fragment","_sfc_main","CommandConsole"],"sources":["../src/dom/focus.ts","../src/dom/selection.ts","../src/dom/keys.ts","../src/dom/scroll.ts","../src/labels.ts","../src/parts/completions-view.ts","../src/parts/block-view.ts","../src/parts/entry-view.ts","../src/use-command-console.ts","../src/command-console.vue","../src/command-console.vue","../src/index.ts"],"sourcesContent":["// A click anywhere in the console focuses the input (README: DOM and styling,\n// C-51), unless the user is selecting text or clicked something of its own.\n\nconst interactive =\n 'a, button, input, select, textarea, [tabindex], [contenteditable]'\n\n/**\n * Whether a click on `target` inside `root` should focus the input: not when\n * text is selected, not inside an interactive element (the input included:\n * a click on it is the browser's).\n */\nexport const clickFocusesInput = (\n target: EventTarget | null,\n root: Element\n): boolean => {\n const selection = root.ownerDocument.getSelection()\n if (selection && !selection.isCollapsed && selection.toString() !== '') {\n return false\n }\n const element =\n target instanceof Element\n ? target\n : target instanceof Node\n ? target.parentElement\n : null\n const found = element?.closest(interactive)\n return !(found && root.contains(found))\n}\n","// Copy is never taken over (README: Keyboard, D-07): with text selected in\n// the input, or in the console's part of the document, Ctrl+C stays the\n// browser's.\nimport type { KeyInput } from '@dolusoft/command-console-core'\n\n/** Whether the document has a non-empty selection inside `root`. */\nconst hasSelectionIn = (root: Element | null): boolean => {\n const selection = root?.ownerDocument.getSelection()\n if (!root || !selection || selection.isCollapsed) {\n return false\n }\n if (selection.toString() === '') {\n return false\n }\n const { anchorNode, focusNode } = selection\n return (\n (anchorNode !== null && root.contains(anchorNode)) ||\n (focusNode !== null && root.contains(focusNode))\n )\n}\n\n/** Where text is selected, as `KeyInput.selection` reports it. */\nexport const selectionOf = (\n input: HTMLInputElement,\n root: Element | null\n): KeyInput['selection'] => {\n if (\n input.selectionStart !== null &&\n input.selectionStart !== input.selectionEnd\n ) {\n return 'input'\n }\n return hasSelectionIn(root) ? 'document' : 'none'\n}\n","// KeyboardEvent → KeyInput (core README: State): the adapter's whole share of\n// the key map. Core never sees a DOM event.\nimport type { KeyInput } from '@dolusoft/command-console-core'\n\nimport { selectionOf } from './selection'\n\n/** The key press `event` on `input`, inside the console `root`. */\nexport const keyInputOf = (\n event: KeyboardEvent,\n input: HTMLInputElement,\n root: Element | null\n): KeyInput => ({\n key: event.key,\n ctrlKey: event.ctrlKey,\n shiftKey: event.shiftKey,\n altKey: event.altKey,\n metaKey: event.metaKey,\n // 229: the key an IME holds while it composes (older engines do not set\n // `isComposing` on the keydown that starts a composition).\n isComposing: event.isComposing || event.keyCode === 229,\n selection: selectionOf(input, root)\n})\n","// Scroll (README: DOM and styling, Q-08, C-58): the log sticks to the bottom\n// while the user is within 2 px of it, and after the user's own submit; new\n// output does not move a view the user scrolled up. `scrollTop` is the only\n// style-related write of the package (P5).\nimport { nextTick, onBeforeUnmount, onMounted, type ShallowRef } from 'vue'\n\n/** How close to the bottom still counts as at the bottom, in px. */\nconst slack = 2\n\nexport interface StickToBottom {\n /** The user's own submit: stick again and show the bottom now. */\n stick(): void\n /** The log's `scroll` event. */\n onScroll(): void\n}\n\nexport const useStickToBottom = (\n log: Readonly<ShallowRef<HTMLElement | null>>,\n entries: Readonly<ShallowRef<HTMLElement | null>>\n): StickToBottom => {\n let stuck = true\n let observer: ResizeObserver | null = null\n let mounted = false\n\n const toBottom = () => {\n const element = log.value\n if (mounted && element) {\n element.scrollTop = element.scrollHeight\n }\n }\n\n onMounted(() => {\n mounted = true\n // One observer on the entries; a late callback after unmount does\n // nothing (D-04).\n if (typeof ResizeObserver !== 'undefined' && entries.value) {\n observer = new ResizeObserver(() => {\n if (stuck) {\n toBottom()\n }\n })\n observer.observe(entries.value)\n }\n })\n\n onBeforeUnmount(() => {\n mounted = false\n observer?.disconnect()\n observer = null\n })\n\n return {\n stick() {\n stuck = true\n void nextTick(toBottom)\n },\n onScroll() {\n const element = log.value\n if (element) {\n stuck =\n element.scrollHeight - element.scrollTop - element.clientHeight <=\n slack\n }\n }\n }\n}\n","// Every text the component renders comes from here (P7, C-47): English\n// defaults, merged one level deep with the `labels` prop.\nimport type { MessageBlock, MessageParams } from '@dolusoft/command-protocol'\n\nimport type { ConsoleLabels, ConsoleLabelsInput } from './contract'\n\n/** The English defaults (README: Labels). */\nconst defaultLabels: ConsoleLabels = {\n input: 'Command',\n inputDescription:\n 'Enter runs the command. Tab completes; on an empty line it leaves the console.',\n log: 'Console output',\n redacted: '(input hidden)',\n cancelledMarker: '^C',\n completions: 'Suggestions',\n busy: 'Running…',\n messages: {\n 'command-not-found': params =>\n params.name === null\n ? 'command not found'\n : `${params.name}: command not found`,\n 'command-failed': 'The command failed.',\n 'unterminated-quote': 'Unterminated quote.',\n interrupted: 'Interrupted.'\n }\n}\n\n/**\n * The defaults with `labels` over them; `messages` merges one level deep, so\n * overriding one message keeps the others.\n */\nexport const resolveLabels = (labels?: ConsoleLabelsInput): ConsoleLabels => ({\n ...defaultLabels,\n ...labels,\n messages: { ...defaultLabels.messages, ...labels?.messages }\n})\n\n/** The text of a message block, from `labels.messages`. */\nexport const messageText = (\n labels: ConsoleLabels,\n block: MessageBlock\n): string => {\n const label = labels.messages[block.code] as\n string | ((params: MessageParams[typeof block.code]) => string)\n if (typeof label === 'string') {\n return label\n }\n // A library-made block of a code with parameters always carries them;\n // `{}` keeps a consumer-made block without them from throwing.\n return label((block.params ?? {}) as never)\n}\n","// The completion list (README: DOM and styling, Completion): a named listbox,\n// one option per candidate, rendered only while a session is open. The ids tie\n// it to the input's `aria-controls` and `aria-activedescendant` (C-48).\nimport { h, type VNode, type VNodeArrayChildren } from 'vue'\n\nimport type { CompletionSession } from '@dolusoft/command-console-core'\n\nimport type { ConsoleSlots } from '../contract'\n\n/** The id of candidate `index` of the list `listId`. */\nexport const optionId = (listId: string, index: number): string =>\n `${listId}-${index}`\n\nexport const renderCompletions = (\n session: CompletionSession,\n listId: string,\n label: string,\n slots: Pick<ConsoleSlots, 'completion'>\n): VNode =>\n h(\n 'ul',\n {\n class: 'cc-completions',\n role: 'listbox',\n id: listId,\n 'aria-label': label\n },\n session.candidates.map((candidate, index) => {\n const active = index === session.active\n return h(\n 'li',\n {\n key: `${index}:${candidate}`,\n class: 'cc-completion',\n role: 'option',\n id: optionId(listId, index),\n 'aria-selected': active ? 'true' : 'false'\n },\n slots.completion\n ? (slots.completion({\n candidate,\n index,\n active\n }) as VNodeArrayChildren)\n : candidate\n )\n })\n )\n","// One output block (README: DOM and styling): `.cc-block` with its `data-type`,\n// in the element of its type, or a `div` around the consumer's `block-<type>`\n// slot (C-67). All text goes through Vue's escaping; there is no HTML (S-08).\nimport { h, type VNode, type VNodeArrayChildren } from 'vue'\n\nimport type {\n JsonPrimitive,\n OutputBlock,\n OutputEntry\n} from '@dolusoft/command-protocol'\n\nimport type { BlockSlotProps, ConsoleLabels, ConsoleSlots } from '../contract'\nimport { messageText } from '../labels'\n\n/** The `block-<type>` slots the component was given. */\nexport type BlockSlots = Pick<\n ConsoleSlots,\n | 'block-text'\n | 'block-json'\n | 'block-table'\n | 'block-list'\n | 'block-message'\n | 'block-custom'\n>\n\nconst json = (value: unknown) => JSON.stringify(value, null, 2)\n\n/** A table cell: a missing key and `null` are empty. */\nconst cellText = (value: JsonPrimitive | undefined): string =>\n value === undefined || value === null ? '' : String(value)\n\nconst content = (block: OutputBlock, labels: ConsoleLabels): VNode => {\n const hooks = { class: 'cc-block', 'data-type': block.type }\n switch (block.type) {\n case 'text':\n return h('pre', hooks, block.text)\n case 'json':\n return h('pre', hooks, json(block.value))\n case 'table':\n return h('table', hooks, [\n h(\n 'thead',\n h(\n 'tr',\n block.columns.map(column => h('th', { scope: 'col' }, column.label))\n )\n ),\n h(\n 'tbody',\n block.rows.map(row =>\n h(\n 'tr',\n block.columns.map(column => h('td', cellText(row[column.key])))\n )\n )\n )\n ])\n case 'list':\n return h(\n 'ul',\n hooks,\n block.items.map(item => h('li', item))\n )\n case 'message':\n return h('div', hooks, messageText(labels, block))\n case 'custom':\n return h('pre', hooks, json(block))\n }\n}\n\n/** Renders `block` of `entry`, through its slot when one is given. */\nexport const renderBlock = (\n block: OutputBlock,\n entry: OutputEntry,\n labels: ConsoleLabels,\n slots: BlockSlots\n): VNode => {\n const slot = slots[`block-${block.type}`] as\n ((props: BlockSlotProps) => unknown) | undefined\n if (slot) {\n return h(\n 'div',\n { class: 'cc-block', 'data-type': block.type },\n slot({ block, entry }) as VNodeArrayChildren\n )\n }\n return content(block, labels)\n}\n","// One entry of the log (README: DOM and styling): an ended input line (its\n// prompt, its text or `labels.redacted`, the cancelled marker) or the blocks of\n// an output.\nimport { h, type VNode, type VNodeArrayChildren } from 'vue'\n\nimport type { ConsoleEntry } from '@dolusoft/command-protocol'\n\nimport { renderBlock, type BlockSlots } from './block-view'\nimport type { ConsoleLabels, ConsoleSlots } from '../contract'\n\n/** The slots an entry may use. */\nexport type EntrySlots = BlockSlots & Pick<ConsoleSlots, 'prompt'>\n\n/** `span.cc-prompt` with the `prompt` slot inside, or the prompt text. */\nexport const renderPrompt = (\n prompt: string,\n slots: Pick<ConsoleSlots, 'prompt'>\n): VNode =>\n h(\n 'span',\n { class: 'cc-prompt' },\n slots.prompt ? (slots.prompt({ prompt }) as VNodeArrayChildren) : prompt\n )\n\nexport const renderEntry = (\n entry: ConsoleEntry,\n labels: ConsoleLabels,\n slots: EntrySlots\n): VNode => {\n if (entry.kind === 'input') {\n return h(\n 'div',\n {\n key: entry.id,\n class: 'cc-entry',\n 'data-kind': 'input',\n 'data-status': entry.status,\n 'data-redacted': entry.redacted ? '' : undefined\n },\n [\n renderPrompt(entry.prompt, slots),\n h(\n 'span',\n { class: 'cc-line' },\n entry.redacted || entry.text === null ? labels.redacted : entry.text\n ),\n entry.status === 'cancelled'\n ? h('span', { class: 'cc-cancelled' }, labels.cancelledMarker)\n : null\n ]\n )\n }\n return h(\n 'div',\n {\n key: entry.id,\n class: 'cc-entry',\n 'data-kind': 'output',\n 'data-tone': entry.tone,\n 'aria-live': entry.quiet ? 'off' : undefined\n },\n entry.blocks.map(block => renderBlock(block, entry, labels, slots))\n )\n}\n","// useCommandConsole (README: The composable): a console without markup. It\n// creates the console once, passes reactive option changes to `setOptions`,\n// mirrors every notification into a `shallowRef` and disposes the console with\n// its effect scope. It does no DOM work, so a consumer can draw a template of\n// its own. The Vue package keeps no console state of its own (P15).\nimport {\n onScopeDispose,\n shallowReadonly,\n shallowRef,\n toValue,\n unref,\n watch\n} from 'vue'\n\nimport {\n createConsole,\n type ConsoleOptions,\n type ConsoleState,\n type OptionalFeatures\n} from '@dolusoft/command-console-core'\n\nimport type {\n UseCommandConsoleOptions,\n UseCommandConsoleReturn\n} from './contract'\n\n/** The options that are functions: a ref may hold one, a getter may not. */\nconst functionKeys = [\n 'onEntriesChange',\n 'onRecallChange',\n 'onContextChange',\n 'createId',\n 'beforeRun',\n 'onError',\n 'formatError',\n 'onClose',\n 'isSensitive',\n 'shouldRecord'\n] as const\n\n/** The options that are values: a ref or a getter may give them. */\nconst valueKeys = [\n 'commands',\n 'prompt',\n 'state',\n 'maxEntries',\n 'recallLimit'\n] as const\n\n/**\n * Creates a console from `options` and keeps it in step with them.\n *\n * A value option (`commands`, `prompt`, `state`, `maxEntries`,\n * `recallLimit`) may be a plain value, a ref or a getter. A function option\n * (`beforeRun`, `onError`, …) may be the function or a ref that holds it: a\n * bare function is the option itself, never a getter. `features` and\n * `initialState` are read once.\n *\n * @example\n * ```ts\n * const { console, state } = useCommandConsole({\n * features: consoleFeatures({ recallFeature }),\n * commands: () => commands.value\n * })\n * ```\n */\nexport function useCommandConsole<\n // `{}` and not its constraint, as `createConsole` (ADR 0002).\n // eslint-disable-next-line @typescript-eslint/no-empty-object-type\n T extends Partial<OptionalFeatures> = {}\n>(options: UseCommandConsoleOptions<T>): UseCommandConsoleReturn<T> {\n const resolve = (): ConsoleOptions<T> => {\n const resolved: Record<string, unknown> = {}\n for (const key of valueKeys) {\n resolved[key] = toValue(options[key])\n }\n for (const key of functionKeys) {\n resolved[key] = unref(options[key])\n }\n return resolved as unknown as ConsoleOptions<T>\n }\n\n const console = createConsole<T>({\n ...resolve(),\n features: options.features,\n initialState: toValue(options.initialState)\n })\n\n const state = shallowRef<ConsoleState>(console.getState())\n const unsubscribe = console.subscribe(next => {\n state.value = next\n })\n\n // Every reactive read of `resolve` is a dependency: a change of any option\n // hands the whole set to the console. The features stay those it was\n // created with.\n const stop = watch(resolve, next => {\n console.setOptions(old => ({\n ...next,\n features: old.features,\n initialState: old.initialState\n }))\n })\n\n onScopeDispose(() => {\n stop()\n unsubscribe()\n console.dispose()\n // The snapshot goes with the console: what it held (the live input\n // among it) is not kept.\n state.value = console.getState()\n })\n\n return {\n console: console,\n state: shallowReadonly(state)\n }\n}\n","<script setup lang=\"ts\">\n// <CommandConsole> (README: The component): the composable's console drawn as\n// the DOM of `contract/dom.ts` (README: DOM and styling). The component holds\n// no console state (P15): it renders the snapshot and forwards DOM events to\n// core actions. What it keeps is DOM-bound: element refs, the \"stuck to\n// bottom\" flag, the last value it emitted per controlled slice and the caret\n// it last reported.\nimport {\n computed,\n onMounted,\n toRef,\n useId,\n useTemplateRef,\n watch,\n type FunctionalComponent\n} from 'vue'\n\nimport {\n completionFeature,\n consoleFeatures,\n contextFeature,\n recallFeature,\n type OptionalFeatures,\n type Updater\n} from '@dolusoft/command-console-core'\nimport type { ConsoleEntry, ContextFrame } from '@dolusoft/command-protocol'\n\nimport type {\n ConsoleEmits,\n ConsoleProps,\n ConsoleSlots,\n ConsoleSubmit\n} from './contract'\nimport { clickFocusesInput } from './dom/focus'\nimport { keyInputOf } from './dom/keys'\nimport { useStickToBottom } from './dom/scroll'\nimport { resolveLabels } from './labels'\nimport { renderCompletions, optionId } from './parts/completions-view'\nimport { renderEntry, renderPrompt } from './parts/entry-view'\nimport { useCommandConsole } from './use-command-console'\n\ndefineOptions({ name: 'CommandConsole' })\n\nconst props = withDefaults(defineProps<ConsoleProps>(), {\n prompt: undefined,\n initialState: undefined,\n createId: undefined,\n maxEntries: undefined,\n beforeRun: undefined,\n onError: undefined,\n formatError: undefined,\n isSensitive: undefined,\n recallLimit: undefined,\n shouldRecord: undefined,\n labels: undefined,\n autofocus: false,\n entries: undefined,\n recall: undefined,\n context: undefined\n})\n\nconst emit = defineEmits<ConsoleEmits>()\n\nconst slots = defineSlots<ConsoleSlots>()\n\n// The component always uses every optional feature (README: The component).\nconst features: OptionalFeatures = consoleFeatures({\n recallFeature,\n completionFeature,\n contextFeature\n})\n\n// --- v-model (core README: Controlled slices, C-61) -------------------------\n// An updater is resolved against the value this component last emitted for\n// the slice, else the prop: a second action in the same tick builds on the\n// first one's value, not on a prop the parent has not re-rendered yet. A new\n// prop value clears it.\ntype Slice = 'entries' | 'recall' | 'context'\ninterface SliceValues {\n entries: ConsoleEntry[]\n recall: string[]\n context: ContextFrame[]\n}\nconst lastEmitted: Partial<SliceValues> = {}\nfor (const slice of ['entries', 'recall', 'context'] as const) {\n watch(\n () => props[slice],\n () => {\n delete lastEmitted[slice]\n },\n { flush: 'sync' }\n )\n}\nconst report =\n <K extends Slice>(slice: K, send: (value: SliceValues[K]) => void) =>\n (updater: Updater<SliceValues[K]>) => {\n const base = (lastEmitted[slice] ?? props[slice] ?? []) as SliceValues[K]\n const value = typeof updater === 'function' ? updater(base) : updater\n lastEmitted[slice] = value\n send(value)\n }\n\n/** The slices bound with `v-model`; the others are the console's. */\nconst controlled = () => {\n const state: Partial<SliceValues> = {}\n if (props.entries !== undefined) {\n state.entries = props.entries\n }\n if (props.recall !== undefined) {\n state.recall = props.recall\n }\n if (props.context !== undefined) {\n state.context = props.context\n }\n return state\n}\n\nconst { console, state } = useCommandConsole({\n features,\n commands: () => props.commands,\n prompt: () => props.prompt,\n state: controlled,\n maxEntries: () => props.maxEntries,\n recallLimit: () => props.recallLimit,\n initialState: props.initialState,\n createId: toRef(props, 'createId'),\n beforeRun: toRef(props, 'beforeRun'),\n onError: toRef(props, 'onError'),\n formatError: toRef(props, 'formatError'),\n isSensitive: toRef(props, 'isSensitive'),\n shouldRecord: toRef(props, 'shouldRecord'),\n onEntriesChange: report('entries', value => emit('update:entries', value)),\n onRecallChange: report('recall', value => emit('update:recall', value)),\n onContextChange: report('context', value => emit('update:context', value)),\n onClose: () => emit('close')\n})\n\n// --- What the template draws ------------------------------------------------\n\nconst labels = computed(() => resolveLabels(props.labels))\nconst listId = useId()\n\nconst running = computed(() => state.value.running)\nconst session = computed(() => {\n const completion = state.value.completion\n return completion?.status === 'open' ? completion : null\n})\nconst activeId = computed(() =>\n session.value && session.value.active >= 0\n ? optionId(listId, session.value.active)\n : undefined\n)\n/** The prompt of the live line: the top context frame's, or the root's. */\nconst prompt = computed(() => {\n void state.value.context\n void props.prompt\n return console.getPrompt()\n})\n/**\n * The recalled line, for the status element (README: Recall): a screen reader\n * does not announce a scripted value change in a focused field.\n */\nconst recalled = computed(() => {\n const { recall, recallCursor } = state.value\n return recallCursor !== null ? (recall[recallCursor] ?? '') : ''\n})\n\nconst entriesView: FunctionalComponent = () =>\n state.value.entries.map(entry => renderEntry(entry, labels.value, slots))\nconst completionsView: FunctionalComponent = () =>\n session.value\n ? renderCompletions(session.value, listId, labels.value.completions, slots)\n : null\nconst promptView: FunctionalComponent = () => renderPrompt(prompt.value, slots)\n\n// --- Elements ---------------------------------------------------------------\n\nconst rootElement = useTemplateRef<HTMLDivElement>('root')\nconst logElement = useTemplateRef<HTMLDivElement>('log')\nconst entriesElement = useTemplateRef<HTMLDivElement>('entries')\nconst inputElement = useTemplateRef<HTMLInputElement>('input')\n\nconst scroll = useStickToBottom(logElement, entriesElement)\n\nconst focusInput = () => inputElement.value?.focus()\n\nonMounted(() => {\n if (props.autofocus) {\n focusInput()\n }\n})\n\n// C-51: a click anywhere in the console focuses the input, unless text is\n// selected or the click was on something interactive.\nconst onRootClick = (event: MouseEvent) => {\n if (rootElement.value && clickFocusesInput(event.target, rootElement.value)) {\n focusInput()\n }\n}\n\n// --- The input element (README: DOM and styling, C-54) ----------------------\n// The element is keyed by `input.revision`. When it is replaced, the new\n// one gets focus only if the old one had it, and the caret goes to the end.\n// Its value is written as a DOM property here, never bound in the template:\n// Vue mirrors a bound `value` into the `value` attribute, which would put\n// the typed line into the DOM.\n\nlet hadFocus = false\nwatch(\n () => state.value.input.revision,\n () => {\n const element = inputElement.value\n hadFocus =\n element !== null && element.ownerDocument.activeElement === element\n // The element about to go may hold a sensitive line (C-36).\n if (element) {\n element.value = ''\n }\n },\n { flush: 'pre' }\n)\n\n/** The value and caret this component last reported (a user edit). */\nlet reported = { value: '', caret: 0 }\n\n/**\n * Writes the console's value into the element. A write by the console\n * (completion, recall) also sets the caret it chose; the user's own edit is\n * left alone, so a selection the user made stays.\n */\nconst syncElement = (replaced: boolean) => {\n const element = inputElement.value\n if (!element) {\n return\n }\n const { value, caret } = state.value.input\n if (element.value !== value) {\n element.value = value\n }\n if (replaced) {\n if (hadFocus) {\n element.focus()\n }\n hadFocus = false\n element.setSelectionRange(value.length, value.length)\n return\n }\n if (\n element.ownerDocument.activeElement !== element ||\n (value === reported.value && caret === reported.caret)\n ) {\n return\n }\n if (element.selectionStart !== caret || element.selectionEnd !== caret) {\n element.setSelectionRange(caret, caret)\n }\n}\nonMounted(() => {\n syncElement(false)\n})\nwatch(\n () => state.value.input,\n (next, previous) => {\n syncElement(next.revision !== previous.revision)\n },\n { flush: 'post' }\n)\n\n/** Reports the element's value and caret to the console (a user edit). */\nconst reportEdit = (element: HTMLInputElement) => {\n const caret = element.selectionStart ?? element.value.length\n reported = { value: element.value, caret }\n console.editInput(element.value, caret)\n}\n\nconst onInput = (event: Event) => {\n reportEdit(event.currentTarget as HTMLInputElement)\n}\n\n// A caret move the user made (click, arrow keys, selection).\nconst onCaret = (event: Event) => {\n const element = event.currentTarget as HTMLInputElement\n const caret = element.selectionStart ?? element.value.length\n if (element.value !== state.value.input.value) {\n return\n }\n if (caret !== state.value.input.caret) {\n reportEdit(element)\n }\n}\n\n// --- Submits (C-64) ---------------------------------------------------------\n\n/**\n * Runs `action` and emits `submit` for the input entry it added, if any;\n * keeps the log at the bottom after the user's own submit (C-58).\n */\nconst submitting = <T,>(action: () => T): T => {\n const before = new Set(state.value.entries.map(entry => entry.id))\n const result = action()\n const added = console\n .getState()\n .entries.find(\n entry =>\n entry.kind === 'input' &&\n entry.status === 'submitted' &&\n !before.has(entry.id)\n )\n if (added) {\n scroll.stick()\n const submit: ConsoleSubmit = {\n inputId: added.id,\n sensitive: added.kind === 'input' && added.redacted\n }\n emit('submit', submit)\n }\n return result\n}\n\nconst onKeydown = (event: KeyboardEvent) => {\n const element = event.currentTarget as HTMLInputElement\n const key = keyInputOf(event, element, rootElement.value)\n if (!key.isComposing) {\n // The caret may have moved without an event the console saw.\n onCaret(event)\n }\n const handled = submitting(() => console.handleKey(key))\n if (handled) {\n event.preventDefault()\n }\n}\n\n// --- Expose ------------------------------------------------------------------\n\nconst exposed = {\n focus: () => {\n focusInput()\n },\n run: (line: string) => submitting(() => console.run(line)),\n cancel: () => {\n console.cancel()\n },\n reset: () => {\n console.reset()\n },\n clear: () => {\n console.clearEntries()\n },\n console,\n state\n}\ndefineExpose(exposed)\n</script>\n\n<template>\n <div\n ref=\"root\"\n class=\"cc-console\"\n :data-busy=\"running ? '' : undefined\"\n :data-context-depth=\"state.context.length\"\n @click=\"onRootClick\"\n >\n <div\n ref=\"log\"\n class=\"cc-log\"\n role=\"log\"\n :aria-label=\"labels.log\"\n @scroll=\"scroll.onScroll\"\n >\n <div ref=\"entries\" class=\"cc-entries\">\n <component :is=\"entriesView\" />\n </div>\n </div>\n <div class=\"cc-status\" role=\"status\">\n <template v-if=\"running\"\n ><slot name=\"busy\" :running=\"running\">{{ labels.busy }}</slot></template\n ><template v-else>{{ recalled }}</template>\n </div>\n <component :is=\"completionsView\" />\n <div class=\"cc-input-line\">\n <component :is=\"promptView\" />\n <input\n :key=\"state.input.revision\"\n ref=\"input\"\n class=\"cc-input\"\n type=\"text\"\n role=\"combobox\"\n aria-autocomplete=\"list\"\n :aria-expanded=\"session ? 'true' : 'false'\"\n :aria-controls=\"session ? listId : undefined\"\n :aria-activedescendant=\"activeId\"\n :aria-label=\"labels.input\"\n :aria-description=\"labels.inputDescription\"\n autocomplete=\"off\"\n autocorrect=\"off\"\n autocapitalize=\"off\"\n spellcheck=\"false\"\n :readonly=\"running !== null\"\n @input=\"onInput\"\n @keydown=\"onKeydown\"\n @keyup=\"onCaret\"\n @click=\"onCaret\"\n @selectionchange=\"onCaret\"\n />\n </div>\n </div>\n</template>\n","<script setup lang=\"ts\">\n// <CommandConsole> (README: The component): the composable's console drawn as\n// the DOM of `contract/dom.ts` (README: DOM and styling). The component holds\n// no console state (P15): it renders the snapshot and forwards DOM events to\n// core actions. What it keeps is DOM-bound: element refs, the \"stuck to\n// bottom\" flag, the last value it emitted per controlled slice and the caret\n// it last reported.\nimport {\n computed,\n onMounted,\n toRef,\n useId,\n useTemplateRef,\n watch,\n type FunctionalComponent\n} from 'vue'\n\nimport {\n completionFeature,\n consoleFeatures,\n contextFeature,\n recallFeature,\n type OptionalFeatures,\n type Updater\n} from '@dolusoft/command-console-core'\nimport type { ConsoleEntry, ContextFrame } from '@dolusoft/command-protocol'\n\nimport type {\n ConsoleEmits,\n ConsoleProps,\n ConsoleSlots,\n ConsoleSubmit\n} from './contract'\nimport { clickFocusesInput } from './dom/focus'\nimport { keyInputOf } from './dom/keys'\nimport { useStickToBottom } from './dom/scroll'\nimport { resolveLabels } from './labels'\nimport { renderCompletions, optionId } from './parts/completions-view'\nimport { renderEntry, renderPrompt } from './parts/entry-view'\nimport { useCommandConsole } from './use-command-console'\n\ndefineOptions({ name: 'CommandConsole' })\n\nconst props = withDefaults(defineProps<ConsoleProps>(), {\n prompt: undefined,\n initialState: undefined,\n createId: undefined,\n maxEntries: undefined,\n beforeRun: undefined,\n onError: undefined,\n formatError: undefined,\n isSensitive: undefined,\n recallLimit: undefined,\n shouldRecord: undefined,\n labels: undefined,\n autofocus: false,\n entries: undefined,\n recall: undefined,\n context: undefined\n})\n\nconst emit = defineEmits<ConsoleEmits>()\n\nconst slots = defineSlots<ConsoleSlots>()\n\n// The component always uses every optional feature (README: The component).\nconst features: OptionalFeatures = consoleFeatures({\n recallFeature,\n completionFeature,\n contextFeature\n})\n\n// --- v-model (core README: Controlled slices, C-61) -------------------------\n// An updater is resolved against the value this component last emitted for\n// the slice, else the prop: a second action in the same tick builds on the\n// first one's value, not on a prop the parent has not re-rendered yet. A new\n// prop value clears it.\ntype Slice = 'entries' | 'recall' | 'context'\ninterface SliceValues {\n entries: ConsoleEntry[]\n recall: string[]\n context: ContextFrame[]\n}\nconst lastEmitted: Partial<SliceValues> = {}\nfor (const slice of ['entries', 'recall', 'context'] as const) {\n watch(\n () => props[slice],\n () => {\n delete lastEmitted[slice]\n },\n { flush: 'sync' }\n )\n}\nconst report =\n <K extends Slice>(slice: K, send: (value: SliceValues[K]) => void) =>\n (updater: Updater<SliceValues[K]>) => {\n const base = (lastEmitted[slice] ?? props[slice] ?? []) as SliceValues[K]\n const value = typeof updater === 'function' ? updater(base) : updater\n lastEmitted[slice] = value\n send(value)\n }\n\n/** The slices bound with `v-model`; the others are the console's. */\nconst controlled = () => {\n const state: Partial<SliceValues> = {}\n if (props.entries !== undefined) {\n state.entries = props.entries\n }\n if (props.recall !== undefined) {\n state.recall = props.recall\n }\n if (props.context !== undefined) {\n state.context = props.context\n }\n return state\n}\n\nconst { console, state } = useCommandConsole({\n features,\n commands: () => props.commands,\n prompt: () => props.prompt,\n state: controlled,\n maxEntries: () => props.maxEntries,\n recallLimit: () => props.recallLimit,\n initialState: props.initialState,\n createId: toRef(props, 'createId'),\n beforeRun: toRef(props, 'beforeRun'),\n onError: toRef(props, 'onError'),\n formatError: toRef(props, 'formatError'),\n isSensitive: toRef(props, 'isSensitive'),\n shouldRecord: toRef(props, 'shouldRecord'),\n onEntriesChange: report('entries', value => emit('update:entries', value)),\n onRecallChange: report('recall', value => emit('update:recall', value)),\n onContextChange: report('context', value => emit('update:context', value)),\n onClose: () => emit('close')\n})\n\n// --- What the template draws ------------------------------------------------\n\nconst labels = computed(() => resolveLabels(props.labels))\nconst listId = useId()\n\nconst running = computed(() => state.value.running)\nconst session = computed(() => {\n const completion = state.value.completion\n return completion?.status === 'open' ? completion : null\n})\nconst activeId = computed(() =>\n session.value && session.value.active >= 0\n ? optionId(listId, session.value.active)\n : undefined\n)\n/** The prompt of the live line: the top context frame's, or the root's. */\nconst prompt = computed(() => {\n void state.value.context\n void props.prompt\n return console.getPrompt()\n})\n/**\n * The recalled line, for the status element (README: Recall): a screen reader\n * does not announce a scripted value change in a focused field.\n */\nconst recalled = computed(() => {\n const { recall, recallCursor } = state.value\n return recallCursor !== null ? (recall[recallCursor] ?? '') : ''\n})\n\nconst entriesView: FunctionalComponent = () =>\n state.value.entries.map(entry => renderEntry(entry, labels.value, slots))\nconst completionsView: FunctionalComponent = () =>\n session.value\n ? renderCompletions(session.value, listId, labels.value.completions, slots)\n : null\nconst promptView: FunctionalComponent = () => renderPrompt(prompt.value, slots)\n\n// --- Elements ---------------------------------------------------------------\n\nconst rootElement = useTemplateRef<HTMLDivElement>('root')\nconst logElement = useTemplateRef<HTMLDivElement>('log')\nconst entriesElement = useTemplateRef<HTMLDivElement>('entries')\nconst inputElement = useTemplateRef<HTMLInputElement>('input')\n\nconst scroll = useStickToBottom(logElement, entriesElement)\n\nconst focusInput = () => inputElement.value?.focus()\n\nonMounted(() => {\n if (props.autofocus) {\n focusInput()\n }\n})\n\n// C-51: a click anywhere in the console focuses the input, unless text is\n// selected or the click was on something interactive.\nconst onRootClick = (event: MouseEvent) => {\n if (rootElement.value && clickFocusesInput(event.target, rootElement.value)) {\n focusInput()\n }\n}\n\n// --- The input element (README: DOM and styling, C-54) ----------------------\n// The element is keyed by `input.revision`. When it is replaced, the new\n// one gets focus only if the old one had it, and the caret goes to the end.\n// Its value is written as a DOM property here, never bound in the template:\n// Vue mirrors a bound `value` into the `value` attribute, which would put\n// the typed line into the DOM.\n\nlet hadFocus = false\nwatch(\n () => state.value.input.revision,\n () => {\n const element = inputElement.value\n hadFocus =\n element !== null && element.ownerDocument.activeElement === element\n // The element about to go may hold a sensitive line (C-36).\n if (element) {\n element.value = ''\n }\n },\n { flush: 'pre' }\n)\n\n/** The value and caret this component last reported (a user edit). */\nlet reported = { value: '', caret: 0 }\n\n/**\n * Writes the console's value into the element. A write by the console\n * (completion, recall) also sets the caret it chose; the user's own edit is\n * left alone, so a selection the user made stays.\n */\nconst syncElement = (replaced: boolean) => {\n const element = inputElement.value\n if (!element) {\n return\n }\n const { value, caret } = state.value.input\n if (element.value !== value) {\n element.value = value\n }\n if (replaced) {\n if (hadFocus) {\n element.focus()\n }\n hadFocus = false\n element.setSelectionRange(value.length, value.length)\n return\n }\n if (\n element.ownerDocument.activeElement !== element ||\n (value === reported.value && caret === reported.caret)\n ) {\n return\n }\n if (element.selectionStart !== caret || element.selectionEnd !== caret) {\n element.setSelectionRange(caret, caret)\n }\n}\nonMounted(() => {\n syncElement(false)\n})\nwatch(\n () => state.value.input,\n (next, previous) => {\n syncElement(next.revision !== previous.revision)\n },\n { flush: 'post' }\n)\n\n/** Reports the element's value and caret to the console (a user edit). */\nconst reportEdit = (element: HTMLInputElement) => {\n const caret = element.selectionStart ?? element.value.length\n reported = { value: element.value, caret }\n console.editInput(element.value, caret)\n}\n\nconst onInput = (event: Event) => {\n reportEdit(event.currentTarget as HTMLInputElement)\n}\n\n// A caret move the user made (click, arrow keys, selection).\nconst onCaret = (event: Event) => {\n const element = event.currentTarget as HTMLInputElement\n const caret = element.selectionStart ?? element.value.length\n if (element.value !== state.value.input.value) {\n return\n }\n if (caret !== state.value.input.caret) {\n reportEdit(element)\n }\n}\n\n// --- Submits (C-64) ---------------------------------------------------------\n\n/**\n * Runs `action` and emits `submit` for the input entry it added, if any;\n * keeps the log at the bottom after the user's own submit (C-58).\n */\nconst submitting = <T,>(action: () => T): T => {\n const before = new Set(state.value.entries.map(entry => entry.id))\n const result = action()\n const added = console\n .getState()\n .entries.find(\n entry =>\n entry.kind === 'input' &&\n entry.status === 'submitted' &&\n !before.has(entry.id)\n )\n if (added) {\n scroll.stick()\n const submit: ConsoleSubmit = {\n inputId: added.id,\n sensitive: added.kind === 'input' && added.redacted\n }\n emit('submit', submit)\n }\n return result\n}\n\nconst onKeydown = (event: KeyboardEvent) => {\n const element = event.currentTarget as HTMLInputElement\n const key = keyInputOf(event, element, rootElement.value)\n if (!key.isComposing) {\n // The caret may have moved without an event the console saw.\n onCaret(event)\n }\n const handled = submitting(() => console.handleKey(key))\n if (handled) {\n event.preventDefault()\n }\n}\n\n// --- Expose ------------------------------------------------------------------\n\nconst exposed = {\n focus: () => {\n focusInput()\n },\n run: (line: string) => submitting(() => console.run(line)),\n cancel: () => {\n console.cancel()\n },\n reset: () => {\n console.reset()\n },\n clear: () => {\n console.clearEntries()\n },\n console,\n state\n}\ndefineExpose(exposed)\n</script>\n\n<template>\n <div\n ref=\"root\"\n class=\"cc-console\"\n :data-busy=\"running ? '' : undefined\"\n :data-context-depth=\"state.context.length\"\n @click=\"onRootClick\"\n >\n <div\n ref=\"log\"\n class=\"cc-log\"\n role=\"log\"\n :aria-label=\"labels.log\"\n @scroll=\"scroll.onScroll\"\n >\n <div ref=\"entries\" class=\"cc-entries\">\n <component :is=\"entriesView\" />\n </div>\n </div>\n <div class=\"cc-status\" role=\"status\">\n <template v-if=\"running\"\n ><slot name=\"busy\" :running=\"running\">{{ labels.busy }}</slot></template\n ><template v-else>{{ recalled }}</template>\n </div>\n <component :is=\"completionsView\" />\n <div class=\"cc-input-line\">\n <component :is=\"promptView\" />\n <input\n :key=\"state.input.revision\"\n ref=\"input\"\n class=\"cc-input\"\n type=\"text\"\n role=\"combobox\"\n aria-autocomplete=\"list\"\n :aria-expanded=\"session ? 'true' : 'false'\"\n :aria-controls=\"session ? listId : undefined\"\n :aria-activedescendant=\"activeId\"\n :aria-label=\"labels.input\"\n :aria-description=\"labels.inputDescription\"\n autocomplete=\"off\"\n autocorrect=\"off\"\n autocapitalize=\"off\"\n spellcheck=\"false\"\n :readonly=\"running !== null\"\n @input=\"onInput\"\n @keydown=\"onKeydown\"\n @keyup=\"onCaret\"\n @click=\"onCaret\"\n @selectionchange=\"onCaret\"\n />\n </div>\n </div>\n</template>\n","// @dolusoft/command-console: a headless command console for Vue 3.\n// `useCommandConsole` is the console without markup; `CommandConsole` draws\n// it as the DOM of `contract/dom.ts`. No CSS (P5).\nimport CommandConsole from './command-console.vue'\n\nexport { CommandConsole }\nexport default CommandConsole\n\nexport { useCommandConsole } from './use-command-console'\n\nexport type * from './contract'\n"],"mappings":";;;AAGA,IAAM,cACJ;;;;;;AAOF,IAAa,qBACX,QACA,SACY;CACZ,MAAM,YAAY,KAAK,cAAc,aAAa;CAClD,IAAI,aAAa,CAAC,UAAU,eAAe,UAAU,SAAS,MAAM,IAClE,OAAO;CAQT,MAAM,SALJ,kBAAkB,UACd,SACA,kBAAkB,OAChB,OAAO,gBACP,KAAA,EACe,QAAQ,WAAW;CAC1C,OAAO,EAAE,SAAS,KAAK,SAAS,KAAK;AACvC;;;ACrBA,IAAM,kBAAkB,SAAkC;CACxD,MAAM,YAAY,MAAM,cAAc,aAAa;CACnD,IAAI,CAAC,QAAQ,CAAC,aAAa,UAAU,aACnC,OAAO;CAET,IAAI,UAAU,SAAS,MAAM,IAC3B,OAAO;CAET,MAAM,EAAE,YAAY,cAAc;CAClC,OACG,eAAe,QAAQ,KAAK,SAAS,UAAU,KAC/C,cAAc,QAAQ,KAAK,SAAS,SAAS;AAElD;;AAGA,IAAa,eACX,OACA,SAC0B;CAC1B,IACE,MAAM,mBAAmB,QACzB,MAAM,mBAAmB,MAAM,cAE/B,OAAO;CAET,OAAO,eAAe,IAAI,IAAI,aAAa;AAC7C;;;AC1BA,IAAa,cACX,OACA,OACA,UACc;CACd,KAAK,MAAM;CACX,SAAS,MAAM;CACf,UAAU,MAAM;CAChB,QAAQ,MAAM;CACd,SAAS,MAAM;CAGf,aAAa,MAAM,eAAe,MAAM,YAAY;CACpD,WAAW,YAAY,OAAO,IAAI;AACpC;;;ACdA,IAAM,QAAQ;AASd,IAAa,oBACX,KACA,YACkB;CAClB,IAAI,QAAQ;CACZ,IAAI,WAAkC;CACtC,IAAI,UAAU;CAEd,MAAM,iBAAiB;EACrB,MAAM,UAAU,IAAI;EACpB,IAAI,WAAW,SACb,QAAQ,YAAY,QAAQ;CAEhC;CAEA,gBAAgB;EACd,UAAU;EAGV,IAAI,OAAO,mBAAmB,eAAe,QAAQ,OAAO;GAC1D,WAAW,IAAI,qBAAqB;IAClC,IAAI,OACF,SAAS;GAEb,CAAC;GACD,SAAS,QAAQ,QAAQ,KAAK;EAChC;CACF,CAAC;CAED,sBAAsB;EACpB,UAAU;EACV,UAAU,WAAW;EACrB,WAAW;CACb,CAAC;CAED,OAAO;EACL,QAAQ;GACN,QAAQ;GACR,SAAc,QAAQ;EACxB;EACA,WAAW;GACT,MAAM,UAAU,IAAI;GACpB,IAAI,SACF,QACE,QAAQ,eAAe,QAAQ,YAAY,QAAQ,gBACnD;EAEN;CACF;AACF;;;AC1DA,IAAM,gBAA+B;CACnC,OAAO;CACP,kBACE;CACF,KAAK;CACL,UAAU;CACV,iBAAiB;CACjB,aAAa;CACb,MAAM;CACN,UAAU;EACR,sBAAqB,WACnB,OAAO,SAAS,OACZ,sBACA,GAAG,OAAO,KAAK;EACrB,kBAAkB;EAClB,sBAAsB;EACtB,aAAa;CACf;AACF;;;;;AAMA,IAAa,iBAAiB,YAAgD;CAC5E,GAAG;CACH,GAAG;CACH,UAAU;EAAE,GAAG,cAAc;EAAU,GAAG,QAAQ;CAAS;AAC7D;;AAGA,IAAa,eACX,QACA,UACW;CACX,MAAM,QAAQ,OAAO,SAAS,MAAM;CAEpC,IAAI,OAAO,UAAU,UACnB,OAAO;CAIT,OAAO,MAAO,MAAM,UAAU,CAAC,CAAW;AAC5C;;;ACxCA,IAAa,YAAY,QAAgB,UACvC,GAAG,OAAO,GAAG;AAEf,IAAa,qBACX,SACA,QACA,OACA,UAEA,EACE,MACA;CACE,OAAO;CACP,MAAM;CACN,IAAI;CACJ,cAAc;AAChB,GACA,QAAQ,WAAW,KAAK,WAAW,UAAU;CAC3C,MAAM,SAAS,UAAU,QAAQ;CACjC,OAAO,EACL,MACA;EACE,KAAK,GAAG,MAAM,GAAG;EACjB,OAAO;EACP,MAAM;EACN,IAAI,SAAS,QAAQ,KAAK;EAC1B,iBAAiB,SAAS,SAAS;CACrC,GACA,MAAM,aACD,MAAM,WAAW;EAChB;EACA;EACA;CACF,CAAC,IACD,SACN;AACF,CAAC,CACH;;ACtBF,IAAM,QAAQ,UAAmB,KAAK,UAAU,OAAO,MAAM,CAAC;;AAG9D,IAAM,YAAY,UAChB,UAAU,KAAA,KAAa,UAAU,OAAO,KAAK,OAAO,KAAK;AAE3D,IAAM,WAAW,OAAoB,WAAiC;CACpE,MAAM,QAAQ;EAAE,OAAO;EAAY,aAAa,MAAM;CAAK;CAC3D,QAAQ,MAAM,MAAd;EACE,KAAK,QACH,OAAO,EAAE,OAAO,OAAO,MAAM,IAAI;EACnC,KAAK,QACH,OAAO,EAAE,OAAO,OAAO,KAAK,MAAM,KAAK,CAAC;EAC1C,KAAK,SACH,OAAO,EAAE,SAAS,OAAO,CACvB,EACE,SACA,EACE,MACA,MAAM,QAAQ,KAAI,WAAU,EAAE,MAAM,EAAE,OAAO,MAAM,GAAG,OAAO,KAAK,CAAC,CACrE,CACF,GACA,EACE,SACA,MAAM,KAAK,KAAI,QACb,EACE,MACA,MAAM,QAAQ,KAAI,WAAU,EAAE,MAAM,SAAS,IAAI,OAAO,IAAI,CAAC,CAAC,CAChE,CACF,CACF,CACF,CAAC;EACH,KAAK,QACH,OAAO,EACL,MACA,OACA,MAAM,MAAM,KAAI,SAAQ,EAAE,MAAM,IAAI,CAAC,CACvC;EACF,KAAK,WACH,OAAO,EAAE,OAAO,OAAO,YAAY,QAAQ,KAAK,CAAC;EACnD,KAAK,UACH,OAAO,EAAE,OAAO,OAAO,KAAK,KAAK,CAAC;CACtC;AACF;;AAGA,IAAa,eACX,OACA,OACA,QACA,UACU;CACV,MAAM,OAAO,MAAM,SAAS,MAAM;CAElC,IAAI,MACF,OAAO,EACL,OACA;EAAE,OAAO;EAAY,aAAa,MAAM;CAAK,GAC7C,KAAK;EAAE;EAAO;CAAM,CAAC,CACvB;CAEF,OAAO,QAAQ,OAAO,MAAM;AAC9B;;;ACzEA,IAAa,gBACX,QACA,UAEA,EACE,QACA,EAAE,OAAO,YAAY,GACrB,MAAM,SAAU,MAAM,OAAO,EAAE,OAAO,CAAC,IAA2B,MACpE;AAEF,IAAa,eACX,OACA,QACA,UACU;CACV,IAAI,MAAM,SAAS,SACjB,OAAO,EACL,OACA;EACE,KAAK,MAAM;EACX,OAAO;EACP,aAAa;EACb,eAAe,MAAM;EACrB,iBAAiB,MAAM,WAAW,KAAK,KAAA;CACzC,GACA;EACE,aAAa,MAAM,QAAQ,KAAK;EAChC,EACE,QACA,EAAE,OAAO,UAAU,GACnB,MAAM,YAAY,MAAM,SAAS,OAAO,OAAO,WAAW,MAAM,IAClE;EACA,MAAM,WAAW,cACb,EAAE,QAAQ,EAAE,OAAO,eAAe,GAAG,OAAO,eAAe,IAC3D;CACN,CACF;CAEF,OAAO,EACL,OACA;EACE,KAAK,MAAM;EACX,OAAO;EACP,aAAa;EACb,aAAa,MAAM;EACnB,aAAa,MAAM,QAAQ,QAAQ,KAAA;CACrC,GACA,MAAM,OAAO,KAAI,UAAS,YAAY,OAAO,OAAO,QAAQ,KAAK,CAAC,CACpE;AACF;;;ACpCA,IAAM,eAAe;CACnB;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF;;AAGA,IAAM,YAAY;CAChB;CACA;CACA;CACA;CACA;AACF;;;;;;;;;;;;;;;;;;AAmBA,SAAgB,kBAId,SAAkE;CAClE,MAAM,gBAAmC;EACvC,MAAM,WAAoC,CAAC;EAC3C,KAAK,MAAM,OAAO,WAChB,SAAS,OAAO,QAAQ,QAAQ,IAAI;EAEtC,KAAK,MAAM,OAAO,cAChB,SAAS,OAAO,MAAM,QAAQ,IAAI;EAEpC,OAAO;CACT;CAEA,MAAM,UAAU,cAAiB;EAC/B,GAAG,QAAQ;EACX,UAAU,QAAQ;EAClB,cAAc,QAAQ,QAAQ,YAAY;CAC5C,CAAC;CAED,MAAM,QAAQ,WAAyB,QAAQ,SAAS,CAAC;CACzD,MAAM,cAAc,QAAQ,WAAU,SAAQ;EAC5C,MAAM,QAAQ;CAChB,CAAC;CAKD,MAAM,OAAO,MAAM,UAAS,SAAQ;EAClC,QAAQ,YAAW,SAAQ;GACzB,GAAG;GACH,UAAU,IAAI;GACd,cAAc,IAAI;EACpB,EAAE;CACJ,CAAC;CAED,qBAAqB;EACnB,KAAK;EACL,YAAY;EACZ,QAAQ,QAAQ;EAGhB,MAAM,QAAQ,QAAQ,SAAS;CACjC,CAAC;CAED,OAAO;EACI;EACT,OAAO,gBAAgB,KAAK;CAC9B;AACF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EC1EA,MAAM,QAAQ;EAkBd,MAAM,OAAO;EAEb,MAAM,QAAQA,SAAA;EAGd,MAAM,WAA6B,gBAAgB;GACjD;GACA;GACA;EACF,CAAC;EAaD,MAAM,cAAoC,CAAC;EAC3C,KAAK,MAAM,SAAS;GAAC;GAAW;GAAU;EAAS,GACjD,YACQ,MAAM,cACN;GACJ,OAAO,YAAY;EACrB,GACA,EAAE,OAAO,OAAO,CAClB;EAEF,MAAM,UACc,OAAU,UAC3B,YAAqC;GACpC,MAAM,OAAQ,YAAY,UAAU,MAAM,UAAU,CAAC;GACrD,MAAM,QAAQ,OAAO,YAAY,aAAa,QAAQ,IAAI,IAAI;GAC9D,YAAY,SAAS;GACrB,KAAK,KAAK;EACZ;;EAGF,MAAM,mBAAmB;GACvB,MAAM,QAA8B,CAAC;GACrC,IAAI,MAAM,YAAY,KAAA,GACpB,MAAM,UAAU,MAAM;GAExB,IAAI,MAAM,WAAW,KAAA,GACnB,MAAM,SAAS,MAAM;GAEvB,IAAI,MAAM,YAAY,KAAA,GACpB,MAAM,UAAU,MAAM;GAExB,OAAO;EACT;EAEA,MAAM,EAAE,SAAS,UAAU,kBAAkB;GAC3C;GACA,gBAAgB,MAAM;GACtB,cAAc,MAAM;GACpB,OAAO;GACP,kBAAkB,MAAM;GACxB,mBAAmB,MAAM;GACzB,cAAc,MAAM;GACpB,UAAU,MAAM,OAAO,UAAU;GACjC,WAAW,MAAM,OAAO,WAAW;GACnC,SAAS,MAAM,OAAO,SAAS;GAC/B,aAAa,MAAM,OAAO,aAAa;GACvC,aAAa,MAAM,OAAO,aAAa;GACvC,cAAc,MAAM,OAAO,cAAc;GACzC,iBAAiB,OAAO,YAAW,UAAS,KAAK,kBAAkB,KAAK,CAAC;GACzE,gBAAgB,OAAO,WAAU,UAAS,KAAK,iBAAiB,KAAK,CAAC;GACtE,iBAAiB,OAAO,YAAW,UAAS,KAAK,kBAAkB,KAAK,CAAC;GACzE,eAAe,KAAK,OAAO;EAC7B,CAAC;EAID,MAAM,SAAS,eAAe,cAAc,MAAM,MAAM,CAAC;EACzD,MAAM,SAAS,MAAM;EAErB,MAAM,UAAU,eAAe,MAAM,MAAM,OAAO;EAClD,MAAM,UAAU,eAAe;GAC7B,MAAM,aAAa,MAAM,MAAM;GAC/B,OAAO,YAAY,WAAW,SAAS,aAAa;EACtD,CAAC;EACD,MAAM,WAAW,eACf,QAAQ,SAAS,QAAQ,MAAM,UAAU,IACrC,SAAS,QAAQ,QAAQ,MAAM,MAAM,IACrC,KAAA,CACN;;EAEA,MAAM,SAAS,eAAe;GAC5B,MAAW,MAAM;GACjB,MAAW;GACX,OAAO,QAAQ,UAAU;EAC3B,CAAC;;;;;EAKD,MAAM,WAAW,eAAe;GAC9B,MAAM,EAAE,QAAQ,iBAAiB,MAAM;GACvC,OAAO,iBAAiB,OAAQ,OAAO,iBAAiB,KAAM;EAChE,CAAC;EAED,MAAM,oBACJ,MAAM,MAAM,QAAQ,KAAI,UAAS,YAAY,OAAO,OAAO,OAAO,KAAK,CAAC;EAC1E,MAAM,wBACJ,QAAQ,QACJ,kBAAkB,QAAQ,OAAO,QAAQ,OAAO,MAAM,aAAa,KAAK,IACxE;EACN,MAAM,mBAAwC,aAAa,OAAO,OAAO,KAAK;EAI9E,MAAM,cAAc,eAA+B,MAAM;EACzD,MAAM,aAAa,eAA+B,KAAK;EACvD,MAAM,iBAAiB,eAA+B,SAAS;EAC/D,MAAM,eAAe,eAAiC,OAAO;EAE7D,MAAM,SAAS,iBAAiB,YAAY,cAAc;EAE1D,MAAM,mBAAmB,aAAa,OAAO,MAAM;EAEnD,gBAAgB;GACd,IAAI,MAAM,WACR,WAAW;EAEf,CAAC;EAID,MAAM,eAAe,UAAsB;GACzC,IAAI,YAAY,SAAS,kBAAkB,MAAM,QAAQ,YAAY,KAAK,GACxE,WAAW;EAEf;EASA,IAAI,WAAW;EACf,YACQ,MAAM,MAAM,MAAM,gBAClB;GACJ,MAAM,UAAU,aAAa;GAC7B,WACE,YAAY,QAAQ,QAAQ,cAAc,kBAAkB;GAE9D,IAAI,SACF,QAAQ,QAAQ;EAEpB,GACA,EAAE,OAAO,MAAM,CACjB;;EAGA,IAAI,WAAW;GAAE,OAAO;GAAI,OAAO;EAAE;;;;;;EAOrC,MAAM,eAAe,aAAsB;GACzC,MAAM,UAAU,aAAa;GAC7B,IAAI,CAAC,SACH;GAEF,MAAM,EAAE,OAAO,UAAU,MAAM,MAAM;GACrC,IAAI,QAAQ,UAAU,OACpB,QAAQ,QAAQ;GAElB,IAAI,UAAU;IACZ,IAAI,UACF,QAAQ,MAAM;IAEhB,WAAW;IACX,QAAQ,kBAAkB,MAAM,QAAQ,MAAM,MAAM;IACpD;GACF;GACA,IACE,QAAQ,cAAc,kBAAkB,WACvC,UAAU,SAAS,SAAS,UAAU,SAAS,OAEhD;GAEF,IAAI,QAAQ,mBAAmB,SAAS,QAAQ,iBAAiB,OAC/D,QAAQ,kBAAkB,OAAO,KAAK;EAE1C;EACA,gBAAgB;GACd,YAAY,KAAK;EACnB,CAAC;EACD,YACQ,MAAM,MAAM,QACjB,MAAM,aAAa;GAClB,YAAY,KAAK,aAAa,SAAS,QAAQ;EACjD,GACA,EAAE,OAAO,OAAO,CAClB;;EAGA,MAAM,cAAc,YAA8B;GAChD,MAAM,QAAQ,QAAQ,kBAAkB,QAAQ,MAAM;GACtD,WAAW;IAAE,OAAO,QAAQ;IAAO;GAAM;GACzC,QAAQ,UAAU,QAAQ,OAAO,KAAK;EACxC;EAEA,MAAM,WAAW,UAAiB;GAChC,WAAW,MAAM,aAAiC;EACpD;EAGA,MAAM,WAAW,UAAiB;GAChC,MAAM,UAAU,MAAM;GACtB,MAAM,QAAQ,QAAQ,kBAAkB,QAAQ,MAAM;GACtD,IAAI,QAAQ,UAAU,MAAM,MAAM,MAAM,OACtC;GAEF,IAAI,UAAU,MAAM,MAAM,MAAM,OAC9B,WAAW,OAAO;EAEtB;;;;;EAQA,MAAM,cAAkB,WAAuB;GAC7C,MAAM,SAAS,IAAI,IAAI,MAAM,MAAM,QAAQ,KAAI,UAAS,MAAM,EAAE,CAAC;GACjE,MAAM,SAAS,OAAO;GACtB,MAAM,QAAQ,QACX,SAAS,CAAA,CACT,QAAQ,MACP,UACE,MAAM,SAAS,WACf,MAAM,WAAW,eACjB,CAAC,OAAO,IAAI,MAAM,EAAE,CACxB;GACF,IAAI,OAAO;IACT,OAAO,MAAM;IACb,MAAM,SAAwB;KAC5B,SAAS,MAAM;KACf,WAAW,MAAM,SAAS,WAAW,MAAM;IAC7C;IACA,KAAK,UAAU,MAAM;GACvB;GACA,OAAO;EACT;EAEA,MAAM,aAAa,UAAyB;GAC1C,MAAM,UAAU,MAAM;GACtB,MAAM,MAAM,WAAW,OAAO,SAAS,YAAY,KAAK;GACxD,IAAI,CAAC,IAAI,aAEP,QAAQ,KAAK;GAGf,IADgB,iBAAiB,QAAQ,UAAU,GAAG,CAClD,GACF,MAAM,eAAe;EAEzB;EAqBA,SAAa;GAhBX,aAAa;IACX,WAAW;GACb;GACA,MAAM,SAAiB,iBAAiB,QAAQ,IAAI,IAAI,CAAC;GACzD,cAAc;IACZ,QAAQ,OAAO;GACjB;GACA,aAAa;IACX,QAAQ,MAAM;GAChB;GACA,aAAa;IACX,QAAQ,aAAa;GACvB;GACA;GACA;EAEW,CAAO;;GAIlB,OAAAC,UAAA,GAAAC,mBAkDM,OAAA;IAjDJ,KAAI;IACJ,OAAM;IACL,aAAW,QAAA,QAAO,KAAQ,KAAA;IAC1B,sBAAoBC,MAAA,KAAA,CAAK,CAAC,QAAQ;IAClC,SAAO;;IAERC,mBAUM,OAAA;KATJ,KAAI;KACJ,OAAM;KACN,MAAK;KACJ,cAAY,OAAA,MAAO;KACnB,UAAM,OAAA,OAAA,OAAA,MAAE,GAAA,SAAAD,MAAA,MAAA,CAAM,CAAC,YAAPA,MAAA,MAAA,CAAM,CAAC,SAAQ,GAAA,IAAA;I,IAExBC,mBAEM,OAFN,YAEM,EADJH,UAAA,GAAAI,YAA+BC,wBAAf,WAAW,CAAA,E;IAG/BF,mBAIM,OAJN,YAIM,CAHY,QAAA,QACbG,WAA6D,KAAA,QAAA,QAAA,EAA1C,SAAS,QAAA,MAAO,SAA0B,CAArBC,gBAAAC,gBAAA,OAAA,MAAO,IAAI,GAAA,CAAA,C,iBACrDR,UAAA,GAAAC,mBAA0CQ,UAAA,EAAA,KAAA,EAAA,GAAA,CAAtBF,gBAAAC,gBAAA,SAAA,KAAQ,GAAA,CAAA,C;KAE/BR,UAAA,GAAAI,YAAmCC,wBAAnB,eAAe,CAAA;IAC/BF,mBAyBM,OAzBN,YAyBM,EAxBJH,UAAA,GAAAI,YAA8BC,wBAAd,UAAU,CAAA,KAC1BL,UAAA,GAAAC,mBAsBE,SAAA;KArBC,KAAKC,MAAA,KAAA,CAAK,CAAC,MAAM;KAClB,KAAI;KACJ,OAAM;KACN,MAAK;KACL,MAAK;KACL,qBAAkB;KACjB,iBAAe,QAAA,QAAO,SAAA;KACtB,iBAAe,QAAA,QAAUA,MAAA,MAAA,IAAS,KAAA;KAClC,yBAAuB,SAAA;KACvB,cAAY,OAAA,MAAO;KACnB,oBAAkB,OAAA,MAAO;KAC1B,cAAa;KACb,aAAY;KACZ,gBAAe;KACf,YAAW;KACV,UAAU,QAAA,UAAO;KACV;KACE;KACT,SAAO;KACP,SAAO;KACP,mBAAiB;;;;;;;AE5Y1B,IAAA,cAAeS"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Whether a click on `target` inside `
|
|
3
|
-
* text is selected, not inside an interactive element
|
|
2
|
+
* Whether a click on `target` inside `root` should focus the input: not when
|
|
3
|
+
* text is selected, not inside an interactive element (the input included:
|
|
4
|
+
* a click on it is the browser's).
|
|
4
5
|
*/
|
|
5
|
-
export declare const clickFocusesInput: (target: EventTarget | null,
|
|
6
|
+
export declare const clickFocusesInput: (target: EventTarget | null, root: Element) => boolean;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dolusoft/command-console",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.1",
|
|
4
4
|
"description": "A headless command console for Vue 3: a scrollback above one input line, with recall, Tab completion, a context stack and redaction of sensitive lines. Ships no CSS.",
|
|
5
5
|
"private": false,
|
|
6
6
|
"type": "module",
|
|
@@ -40,8 +40,8 @@
|
|
|
40
40
|
],
|
|
41
41
|
"license": "MIT",
|
|
42
42
|
"dependencies": {
|
|
43
|
-
"@dolusoft/command-console-core": "1.0.
|
|
44
|
-
"@dolusoft/command-protocol": "1.0.
|
|
43
|
+
"@dolusoft/command-console-core": "1.0.1",
|
|
44
|
+
"@dolusoft/command-protocol": "1.0.1"
|
|
45
45
|
},
|
|
46
46
|
"devDependencies": {
|
|
47
47
|
"vue": "^3.5.43"
|