@vortexm/vjt 0.1.5 → 0.1.7
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/index.js +332 -40
- package/dist/lib/action-runtime.d.ts +9 -0
- package/dist/lib/dom-state.d.ts +4 -0
- package/dist/lib/widgets/confirm-modal.d.ts +7 -0
- package/dist/lib/widgets/context-menu.d.ts +12 -6
- package/dist/lib/widgets/context.d.ts +2 -6
- package/dist/lib/widgets/delegation.d.ts +10 -0
- package/package.json +1 -1
- package/vjt-styles.css +63 -5
package/dist/index.js
CHANGED
|
@@ -4582,6 +4582,9 @@ var ActionRuntime = class {
|
|
|
4582
4582
|
focusWidget;
|
|
4583
4583
|
playAudio;
|
|
4584
4584
|
stopPlaying;
|
|
4585
|
+
copyToClipboard;
|
|
4586
|
+
selectFile;
|
|
4587
|
+
confirmModal;
|
|
4585
4588
|
startRecording;
|
|
4586
4589
|
stopRecording;
|
|
4587
4590
|
startListening;
|
|
@@ -4616,6 +4619,9 @@ var ActionRuntime = class {
|
|
|
4616
4619
|
this.focusWidget = options.focusWidget;
|
|
4617
4620
|
this.playAudio = options.playAudio;
|
|
4618
4621
|
this.stopPlaying = options.stopPlaying;
|
|
4622
|
+
this.copyToClipboard = options.copyToClipboard;
|
|
4623
|
+
this.selectFile = options.selectFile;
|
|
4624
|
+
this.confirmModal = options.confirmModal;
|
|
4619
4625
|
this.startRecording = options.startRecording;
|
|
4620
4626
|
this.stopRecording = options.stopRecording;
|
|
4621
4627
|
this.startListening = options.startListening;
|
|
@@ -4654,7 +4660,7 @@ var ActionRuntime = class {
|
|
|
4654
4660
|
async runActions(actions, inputValue, context) {
|
|
4655
4661
|
let current = inputValue;
|
|
4656
4662
|
for (const action of actions) {
|
|
4657
|
-
current = await this.runSingleAction(action,
|
|
4663
|
+
current = await this.runSingleAction(action, inputValue, context);
|
|
4658
4664
|
}
|
|
4659
4665
|
return current;
|
|
4660
4666
|
}
|
|
@@ -4664,34 +4670,14 @@ var ActionRuntime = class {
|
|
|
4664
4670
|
async runSingleAction(action, inputValue, context) {
|
|
4665
4671
|
const actionName = action.action.trim();
|
|
4666
4672
|
try {
|
|
4667
|
-
|
|
4673
|
+
const rawOutput = await this.executeAction(action, inputValue, context);
|
|
4674
|
+
logRuntimeDebug(this.debugLogging, "action-output", {
|
|
4675
|
+
action: actionName,
|
|
4676
|
+
output: rawOutput
|
|
4677
|
+
});
|
|
4678
|
+
let output = rawOutput;
|
|
4668
4679
|
if (action.andThen?.length) {
|
|
4669
|
-
if (
|
|
4670
|
-
const collected = [];
|
|
4671
|
-
for (const [index, entry] of output.entries()) {
|
|
4672
|
-
if (entry === null || entry === void 0) {
|
|
4673
|
-
continue;
|
|
4674
|
-
}
|
|
4675
|
-
const nestedOutput = await this.runActions(action.andThen, entry, {
|
|
4676
|
-
...context,
|
|
4677
|
-
currentValue: entry,
|
|
4678
|
-
currentList: output,
|
|
4679
|
-
currentIndex: index
|
|
4680
|
-
});
|
|
4681
|
-
if (nestedOutput !== null && nestedOutput !== void 0) {
|
|
4682
|
-
if (Array.isArray(nestedOutput)) {
|
|
4683
|
-
for (const item of nestedOutput) {
|
|
4684
|
-
if (item !== null && item !== void 0) {
|
|
4685
|
-
collected.push(item);
|
|
4686
|
-
}
|
|
4687
|
-
}
|
|
4688
|
-
} else {
|
|
4689
|
-
collected.push(nestedOutput);
|
|
4690
|
-
}
|
|
4691
|
-
}
|
|
4692
|
-
}
|
|
4693
|
-
output = collected;
|
|
4694
|
-
} else if (output !== null && output !== void 0) {
|
|
4680
|
+
if (output !== null && output !== void 0) {
|
|
4695
4681
|
output = await this.runActions(action.andThen, output, { ...context, currentValue: output });
|
|
4696
4682
|
} else {
|
|
4697
4683
|
output = null;
|
|
@@ -4699,6 +4685,7 @@ var ActionRuntime = class {
|
|
|
4699
4685
|
}
|
|
4700
4686
|
logRuntimeDebug(this.debugLogging, "action-result", {
|
|
4701
4687
|
action: actionName,
|
|
4688
|
+
rawOutput,
|
|
4702
4689
|
output
|
|
4703
4690
|
});
|
|
4704
4691
|
return output;
|
|
@@ -4716,7 +4703,7 @@ var ActionRuntime = class {
|
|
|
4716
4703
|
}
|
|
4717
4704
|
async executeAction(action, inputValue, context) {
|
|
4718
4705
|
const name = action.action.trim();
|
|
4719
|
-
logRuntimeDebug(this.debugLogging, "action", {
|
|
4706
|
+
logRuntimeDebug(this.debugLogging, "action-start", {
|
|
4720
4707
|
action: name,
|
|
4721
4708
|
args: action.args,
|
|
4722
4709
|
inputValue,
|
|
@@ -4755,6 +4742,16 @@ var ActionRuntime = class {
|
|
|
4755
4742
|
await this.playAudio(this.resolveReference(name.slice(5), context.currentValue, context.responseValue));
|
|
4756
4743
|
return null;
|
|
4757
4744
|
}
|
|
4745
|
+
if (name === "copyToClipboard") {
|
|
4746
|
+
await this.copyToClipboard(inputValue);
|
|
4747
|
+
return inputValue;
|
|
4748
|
+
}
|
|
4749
|
+
if (name === "selectFile") {
|
|
4750
|
+
return this.selectFile(action.args);
|
|
4751
|
+
}
|
|
4752
|
+
if (name === "confirmModal") {
|
|
4753
|
+
return await this.confirmModal(action.args, inputValue) ? inputValue : void 0;
|
|
4754
|
+
}
|
|
4758
4755
|
if (name === "stopPlaying") {
|
|
4759
4756
|
await this.stopPlaying();
|
|
4760
4757
|
return null;
|
|
@@ -4785,7 +4782,9 @@ var ActionRuntime = class {
|
|
|
4785
4782
|
this.setActiveContextMenu({
|
|
4786
4783
|
id: menuId,
|
|
4787
4784
|
x: position.x,
|
|
4788
|
-
y: position.y
|
|
4785
|
+
y: position.y,
|
|
4786
|
+
currentValue: context.currentValue,
|
|
4787
|
+
openPath: []
|
|
4789
4788
|
});
|
|
4790
4789
|
return null;
|
|
4791
4790
|
}
|
|
@@ -4827,7 +4826,11 @@ var ActionRuntime = class {
|
|
|
4827
4826
|
return inputValue;
|
|
4828
4827
|
}
|
|
4829
4828
|
if (name.startsWith("get ")) {
|
|
4830
|
-
|
|
4829
|
+
const reference = name.slice(4);
|
|
4830
|
+
if (Array.isArray(context.currentValue) && this.isCurrentScopedReference(reference)) {
|
|
4831
|
+
return context.currentValue.map((entry) => this.resolveReference(reference, entry, context.responseValue)).filter((entry) => entry !== null && entry !== void 0);
|
|
4832
|
+
}
|
|
4833
|
+
return this.resolveReference(reference, context.currentValue, context.responseValue);
|
|
4831
4834
|
}
|
|
4832
4835
|
if (name.startsWith("equals ")) {
|
|
4833
4836
|
return this.resolveReference(name.slice(7), context.currentValue, context.responseValue) === action.args;
|
|
@@ -4848,7 +4851,11 @@ var ActionRuntime = class {
|
|
|
4848
4851
|
return nextValue;
|
|
4849
4852
|
}
|
|
4850
4853
|
if (name.startsWith("filter ")) {
|
|
4851
|
-
const
|
|
4854
|
+
const reference = name.slice(7);
|
|
4855
|
+
if (Array.isArray(context.currentValue) && this.isCurrentScopedReference(reference)) {
|
|
4856
|
+
return context.currentValue.filter((entry) => this.resolveReference(reference, entry, context.responseValue));
|
|
4857
|
+
}
|
|
4858
|
+
const value = this.resolveReference(reference, context.currentValue, context.responseValue);
|
|
4852
4859
|
return value ? inputValue : null;
|
|
4853
4860
|
}
|
|
4854
4861
|
if (name === "remove") {
|
|
@@ -4909,6 +4916,9 @@ var ActionRuntime = class {
|
|
|
4909
4916
|
return inputValue;
|
|
4910
4917
|
}
|
|
4911
4918
|
if (name === "map") {
|
|
4919
|
+
if (Array.isArray(context.currentValue)) {
|
|
4920
|
+
return context.currentValue.map((entry) => this.resolveMappedValue(action.args, entry, context.responseValue));
|
|
4921
|
+
}
|
|
4912
4922
|
return this.resolveMappedValue(action.args, context.currentValue, context.responseValue);
|
|
4913
4923
|
}
|
|
4914
4924
|
if (name === "ifelse") {
|
|
@@ -4951,6 +4961,9 @@ var ActionRuntime = class {
|
|
|
4951
4961
|
}
|
|
4952
4962
|
return JSON.stringify(value);
|
|
4953
4963
|
}
|
|
4964
|
+
isCurrentScopedReference(reference) {
|
|
4965
|
+
return reference === "current" || reference.startsWith("current.") || reference.startsWith("this.") || reference === "this";
|
|
4966
|
+
}
|
|
4954
4967
|
};
|
|
4955
4968
|
function isIfElseArgs(value) {
|
|
4956
4969
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
@@ -6324,6 +6337,21 @@ function renderModalOverlay(env, modalId) {
|
|
|
6324
6337
|
}
|
|
6325
6338
|
|
|
6326
6339
|
// src/lib/widgets/context-menu.ts
|
|
6340
|
+
var MENU_WIDTH = 220;
|
|
6341
|
+
var MENU_ITEM_HEIGHT = 44;
|
|
6342
|
+
var MENU_PADDING = 8;
|
|
6343
|
+
function getContextMenuItemAtPath(items, path) {
|
|
6344
|
+
let currentItems = items ?? [];
|
|
6345
|
+
let currentItem = null;
|
|
6346
|
+
for (const index of path) {
|
|
6347
|
+
currentItem = currentItems[index] ?? null;
|
|
6348
|
+
if (!currentItem) {
|
|
6349
|
+
return null;
|
|
6350
|
+
}
|
|
6351
|
+
currentItems = currentItem.items ?? [];
|
|
6352
|
+
}
|
|
6353
|
+
return currentItem;
|
|
6354
|
+
}
|
|
6327
6355
|
function renderContextMenuOverlay(env, menu) {
|
|
6328
6356
|
const node = env.nodeById.get(menu.id);
|
|
6329
6357
|
if (!node || node.widget !== "context-menu") {
|
|
@@ -6331,8 +6359,52 @@ function renderContextMenuOverlay(env, menu) {
|
|
|
6331
6359
|
}
|
|
6332
6360
|
const state = env.ensureWidgetState(node, menu.id);
|
|
6333
6361
|
const enabled = state.enabled ?? env.normalizeBoolean(node.enabled, true);
|
|
6334
|
-
const
|
|
6335
|
-
|
|
6362
|
+
const openPath = Array.isArray(menu.openPath) ? menu.openPath : [];
|
|
6363
|
+
const layers = [];
|
|
6364
|
+
let items = node.items ?? [];
|
|
6365
|
+
let pathPrefix = [];
|
|
6366
|
+
let layerX = menu.x;
|
|
6367
|
+
let layerY = menu.y;
|
|
6368
|
+
while (items.length > 0) {
|
|
6369
|
+
layers.push(renderMenuLayer(env, menu.id, items, enabled, layerX, layerY, pathPrefix));
|
|
6370
|
+
const nextIndex = openPath[pathPrefix.length];
|
|
6371
|
+
if (typeof nextIndex !== "number") {
|
|
6372
|
+
break;
|
|
6373
|
+
}
|
|
6374
|
+
const nextItem = items[nextIndex];
|
|
6375
|
+
if (!nextItem?.items?.length) {
|
|
6376
|
+
break;
|
|
6377
|
+
}
|
|
6378
|
+
const proposedY = layerY + MENU_PADDING + nextIndex * MENU_ITEM_HEIGHT;
|
|
6379
|
+
const rightX = layerX + MENU_WIDTH - MENU_PADDING;
|
|
6380
|
+
const leftX = layerX - MENU_WIDTH + MENU_PADDING;
|
|
6381
|
+
layerX = rightX + MENU_WIDTH <= window.innerWidth ? rightX : Math.max(8, leftX);
|
|
6382
|
+
layerY = Math.max(8, Math.min(proposedY, window.innerHeight - (16 + nextItem.items.length * MENU_ITEM_HEIGHT) - 8));
|
|
6383
|
+
items = nextItem.items;
|
|
6384
|
+
pathPrefix = [...pathPrefix, nextIndex];
|
|
6385
|
+
}
|
|
6386
|
+
return `<div class="vjt-context-menu-backdrop" data-context-menu-backdrop="${env.escapeHtml(menu.id)}">${layers.join("")}</div>`;
|
|
6387
|
+
}
|
|
6388
|
+
function renderMenuLayer(env, menuId, items, enabled, x, y, pathPrefix) {
|
|
6389
|
+
const content = items.map((item, index) => {
|
|
6390
|
+
const path = [...pathPrefix, index];
|
|
6391
|
+
const pathText = path.join(".");
|
|
6392
|
+
const label = env.escapeHtml(env.resolveI18nValue(item.name));
|
|
6393
|
+
const disabled = enabled ? "" : " disabled";
|
|
6394
|
+
if (item.items?.length) {
|
|
6395
|
+
return `<button class="vjt-context-menu-item vjt-context-menu-item--parent" type="button" data-context-menu-expand="${env.escapeHtml(menuId)}:${env.escapeHtml(pathText)}"${disabled}><span>${label}</span><span class="vjt-context-menu-chevron">></span></button>`;
|
|
6396
|
+
}
|
|
6397
|
+
return `<button class="vjt-context-menu-item" type="button" data-context-menu-item="${env.escapeHtml(menuId)}:${env.escapeHtml(pathText)}"${disabled}>${label}</button>`;
|
|
6398
|
+
}).join("");
|
|
6399
|
+
return `<div class="vjt-context-menu" style="left:${x}px;top:${y}px">${content}</div>`;
|
|
6400
|
+
}
|
|
6401
|
+
|
|
6402
|
+
// src/lib/widgets/confirm-modal.ts
|
|
6403
|
+
function renderConfirmModalOverlay(env, modal) {
|
|
6404
|
+
const caption = env.escapeHtml(env.resolveI18nValue(modal.caption));
|
|
6405
|
+
const yes = env.escapeHtml(env.resolveI18nValue(modal.yes));
|
|
6406
|
+
const no = env.escapeHtml(env.resolveI18nValue(modal.no));
|
|
6407
|
+
return `<div class="vjt-confirm-backdrop" data-confirm-backdrop="true"><div class="vjt-confirm-window"><div class="vjt-confirm-caption">${caption}</div><div class="vjt-confirm-actions"><button class="vjt-button vjt-button--bright" type="button" data-confirm-yes="true">${yes}</button><button class="vjt-button vjt-button--regular" type="button" data-confirm-no="true">${no}</button></div></div></div>`;
|
|
6336
6408
|
}
|
|
6337
6409
|
|
|
6338
6410
|
// src/lib/widgets/bindings.ts
|
|
@@ -6524,7 +6596,7 @@ function bindWidgetEvents(root, env) {
|
|
|
6524
6596
|
});
|
|
6525
6597
|
continue;
|
|
6526
6598
|
}
|
|
6527
|
-
if (element instanceof HTMLAnchorElement && element.dataset.widget === "link" && env.getInlineActions(node)?.length) {
|
|
6599
|
+
if (element instanceof HTMLAnchorElement && element.dataset.widget === "link" && (node.events?.onClick?.length || env.getInlineActions(node)?.length)) {
|
|
6528
6600
|
element.addEventListener("pointerdown", (event) => {
|
|
6529
6601
|
env.setLastPointer({ x: event.clientX, y: event.clientY });
|
|
6530
6602
|
});
|
|
@@ -6594,6 +6666,45 @@ function bindDelegatedUi(root, env) {
|
|
|
6594
6666
|
window.addEventListener("pointercancel", () => {
|
|
6595
6667
|
env.stopSplitterDrag();
|
|
6596
6668
|
});
|
|
6669
|
+
root.addEventListener("pointerover", (event) => {
|
|
6670
|
+
void (async () => {
|
|
6671
|
+
const target = event.target;
|
|
6672
|
+
if (!(target instanceof Element)) {
|
|
6673
|
+
return;
|
|
6674
|
+
}
|
|
6675
|
+
const hoveredItem = target.closest("[data-context-menu-item], [data-context-menu-expand]");
|
|
6676
|
+
if (!(hoveredItem instanceof HTMLButtonElement)) {
|
|
6677
|
+
return;
|
|
6678
|
+
}
|
|
6679
|
+
const descriptor = hoveredItem.dataset.contextMenuExpand ?? hoveredItem.dataset.contextMenuItem;
|
|
6680
|
+
if (!descriptor) {
|
|
6681
|
+
return;
|
|
6682
|
+
}
|
|
6683
|
+
const [menuId, pathText = ""] = descriptor.split(":");
|
|
6684
|
+
const node = env.nodeById.get(menuId);
|
|
6685
|
+
if (node?.widget !== "context-menu") {
|
|
6686
|
+
return;
|
|
6687
|
+
}
|
|
6688
|
+
const activeMenu = env.getActiveContextMenu();
|
|
6689
|
+
if (!activeMenu || activeMenu.id !== menuId) {
|
|
6690
|
+
return;
|
|
6691
|
+
}
|
|
6692
|
+
const path = parseMenuPath(pathText);
|
|
6693
|
+
const item = getContextMenuItemAtPath(node.items, path);
|
|
6694
|
+
if (!item) {
|
|
6695
|
+
return;
|
|
6696
|
+
}
|
|
6697
|
+
const nextOpenPath = item.items?.length ? path : path.slice(0, -1);
|
|
6698
|
+
if (samePath(activeMenu.openPath ?? [], nextOpenPath)) {
|
|
6699
|
+
return;
|
|
6700
|
+
}
|
|
6701
|
+
env.setActiveContextMenu({
|
|
6702
|
+
...activeMenu,
|
|
6703
|
+
openPath: nextOpenPath
|
|
6704
|
+
});
|
|
6705
|
+
await env.rerenderRoot();
|
|
6706
|
+
})();
|
|
6707
|
+
});
|
|
6597
6708
|
root.addEventListener("click", (event) => {
|
|
6598
6709
|
void (async () => {
|
|
6599
6710
|
const target = event.target;
|
|
@@ -6672,7 +6783,7 @@ function bindDelegatedUi(root, env) {
|
|
|
6672
6783
|
}
|
|
6673
6784
|
const menuItem = target.closest("[data-context-menu-item]");
|
|
6674
6785
|
if (menuItem instanceof HTMLButtonElement) {
|
|
6675
|
-
const [menuId,
|
|
6786
|
+
const [menuId, pathText = ""] = (menuItem.dataset.contextMenuItem ?? "").split(":");
|
|
6676
6787
|
const node = env.nodeById.get(menuId);
|
|
6677
6788
|
if (node?.widget !== "context-menu") {
|
|
6678
6789
|
return;
|
|
@@ -6680,11 +6791,27 @@ function bindDelegatedUi(root, env) {
|
|
|
6680
6791
|
if (!env.isWidgetEnabled(node, menuId)) {
|
|
6681
6792
|
return;
|
|
6682
6793
|
}
|
|
6683
|
-
const item = node.items
|
|
6794
|
+
const item = getContextMenuItemAtPath(node.items, parseMenuPath(pathText));
|
|
6795
|
+
const menuContext = env.getActiveContextMenu();
|
|
6796
|
+
const currentValue = menuContext?.id === menuId ? menuContext.currentValue ?? null : null;
|
|
6684
6797
|
env.setActiveContextMenu(null);
|
|
6685
6798
|
if (item?.actions?.length) {
|
|
6686
|
-
await env.runActions(item.actions,
|
|
6799
|
+
await env.runActions(item.actions, currentValue, { currentValue });
|
|
6800
|
+
}
|
|
6801
|
+
await env.rerenderRoot();
|
|
6802
|
+
return;
|
|
6803
|
+
}
|
|
6804
|
+
const menuParentItem = target.closest("[data-context-menu-expand]");
|
|
6805
|
+
if (menuParentItem instanceof HTMLButtonElement) {
|
|
6806
|
+
const [menuId, pathText = ""] = (menuParentItem.dataset.contextMenuExpand ?? "").split(":");
|
|
6807
|
+
const activeMenu = env.getActiveContextMenu();
|
|
6808
|
+
if (!activeMenu || activeMenu.id !== menuId) {
|
|
6809
|
+
return;
|
|
6687
6810
|
}
|
|
6811
|
+
env.setActiveContextMenu({
|
|
6812
|
+
...activeMenu,
|
|
6813
|
+
openPath: parseMenuPath(pathText)
|
|
6814
|
+
});
|
|
6688
6815
|
await env.rerenderRoot();
|
|
6689
6816
|
return;
|
|
6690
6817
|
}
|
|
@@ -6703,10 +6830,32 @@ function bindDelegatedUi(root, env) {
|
|
|
6703
6830
|
await env.runActions(item.actions, null, { currentValue: null });
|
|
6704
6831
|
}
|
|
6705
6832
|
await env.rerenderRoot();
|
|
6833
|
+
return;
|
|
6834
|
+
}
|
|
6835
|
+
if (target.closest("[data-confirm-yes]")) {
|
|
6836
|
+
env.resolveConfirmModal(true);
|
|
6837
|
+
await env.rerenderRoot();
|
|
6838
|
+
return;
|
|
6839
|
+
}
|
|
6840
|
+
if (target.closest("[data-confirm-no]")) {
|
|
6841
|
+
env.resolveConfirmModal(false);
|
|
6842
|
+
await env.rerenderRoot();
|
|
6706
6843
|
}
|
|
6707
6844
|
})();
|
|
6708
6845
|
});
|
|
6709
6846
|
}
|
|
6847
|
+
function parseMenuPath(value) {
|
|
6848
|
+
if (!value) {
|
|
6849
|
+
return [];
|
|
6850
|
+
}
|
|
6851
|
+
return value.split(".").map((part) => Number.parseInt(part, 10)).filter((part) => !Number.isNaN(part));
|
|
6852
|
+
}
|
|
6853
|
+
function samePath(left, right) {
|
|
6854
|
+
if (left.length !== right.length) {
|
|
6855
|
+
return false;
|
|
6856
|
+
}
|
|
6857
|
+
return left.every((value, index) => value === right[index]);
|
|
6858
|
+
}
|
|
6710
6859
|
|
|
6711
6860
|
// src/lib/render.ts
|
|
6712
6861
|
var DEFAULT_DATE_FORMAT = "dd.MM.yyyy";
|
|
@@ -6863,6 +7012,7 @@ var RuntimeRenderer = class {
|
|
|
6863
7012
|
hasTriggeredInitialRefresh = false;
|
|
6864
7013
|
activeModalId = null;
|
|
6865
7014
|
activeContextMenu = null;
|
|
7015
|
+
activeConfirmModal = null;
|
|
6866
7016
|
lastPointer = { x: 24, y: 24 };
|
|
6867
7017
|
pendingResetModalIds = /* @__PURE__ */ new Set();
|
|
6868
7018
|
constructor(description, options = {}) {
|
|
@@ -6966,6 +7116,9 @@ var RuntimeRenderer = class {
|
|
|
6966
7116
|
focusWidget: (reference) => this.focusWidget(reference),
|
|
6967
7117
|
playAudio: (value) => this.voiceRuntime.play(value),
|
|
6968
7118
|
stopPlaying: () => this.voiceRuntime.stopPlaying(),
|
|
7119
|
+
copyToClipboard: (value) => this.copyToClipboard(value),
|
|
7120
|
+
selectFile: (args) => this.selectFile(args),
|
|
7121
|
+
confirmModal: (args, inputValue) => this.confirmModal(args, inputValue),
|
|
6969
7122
|
startRecording: () => this.voiceRuntime.startRecording(),
|
|
6970
7123
|
stopRecording: () => Promise.resolve(this.voiceRuntime.stopRecording()),
|
|
6971
7124
|
startListening: () => this.voiceRuntime.startListening(),
|
|
@@ -7124,6 +7277,138 @@ var RuntimeRenderer = class {
|
|
|
7124
7277
|
await this.rerenderRoot();
|
|
7125
7278
|
await this.runSystemEvent("onAfterNavigate");
|
|
7126
7279
|
}
|
|
7280
|
+
async copyToClipboard(value) {
|
|
7281
|
+
const text = value == null ? "" : typeof value === "string" ? value : JSON.stringify(value) ?? "";
|
|
7282
|
+
if (typeof navigator !== "undefined" && navigator.clipboard?.writeText) {
|
|
7283
|
+
await navigator.clipboard.writeText(text);
|
|
7284
|
+
return;
|
|
7285
|
+
}
|
|
7286
|
+
if (typeof document === "undefined") {
|
|
7287
|
+
throw new Error("Clipboard API is unavailable");
|
|
7288
|
+
}
|
|
7289
|
+
const textarea = document.createElement("textarea");
|
|
7290
|
+
textarea.value = text;
|
|
7291
|
+
textarea.setAttribute("readonly", "true");
|
|
7292
|
+
textarea.style.position = "fixed";
|
|
7293
|
+
textarea.style.left = "-9999px";
|
|
7294
|
+
textarea.style.top = "0";
|
|
7295
|
+
document.body.append(textarea);
|
|
7296
|
+
textarea.focus();
|
|
7297
|
+
textarea.select();
|
|
7298
|
+
const copied = document.execCommand("copy");
|
|
7299
|
+
textarea.remove();
|
|
7300
|
+
if (!copied) {
|
|
7301
|
+
throw new Error("Failed to copy value to clipboard");
|
|
7302
|
+
}
|
|
7303
|
+
}
|
|
7304
|
+
async selectFile(args) {
|
|
7305
|
+
if (typeof document === "undefined") {
|
|
7306
|
+
throw new Error("File selection is unavailable");
|
|
7307
|
+
}
|
|
7308
|
+
const options = isPlainObject4(args) ? args : {};
|
|
7309
|
+
const input = document.createElement("input");
|
|
7310
|
+
input.type = "file";
|
|
7311
|
+
input.style.position = "fixed";
|
|
7312
|
+
input.style.left = "-9999px";
|
|
7313
|
+
input.style.top = "0";
|
|
7314
|
+
if (typeof options.accept === "string" && options.accept.trim()) {
|
|
7315
|
+
input.accept = options.accept;
|
|
7316
|
+
}
|
|
7317
|
+
input.multiple = options.multiple === true;
|
|
7318
|
+
const selection = new Promise((resolve, reject) => {
|
|
7319
|
+
let settled = false;
|
|
7320
|
+
const finish = (value) => {
|
|
7321
|
+
if (settled) {
|
|
7322
|
+
return;
|
|
7323
|
+
}
|
|
7324
|
+
settled = true;
|
|
7325
|
+
resolve(value);
|
|
7326
|
+
};
|
|
7327
|
+
const fail = (error) => {
|
|
7328
|
+
if (settled) {
|
|
7329
|
+
return;
|
|
7330
|
+
}
|
|
7331
|
+
settled = true;
|
|
7332
|
+
reject(error instanceof Error ? error : new Error(String(error)));
|
|
7333
|
+
};
|
|
7334
|
+
const cleanup = () => {
|
|
7335
|
+
window.removeEventListener("focus", handleFocus, true);
|
|
7336
|
+
input.remove();
|
|
7337
|
+
};
|
|
7338
|
+
const handleFocus = () => {
|
|
7339
|
+
window.setTimeout(() => {
|
|
7340
|
+
if (!settled && (!input.files || input.files.length === 0)) {
|
|
7341
|
+
cleanup();
|
|
7342
|
+
finish(null);
|
|
7343
|
+
}
|
|
7344
|
+
}, 250);
|
|
7345
|
+
};
|
|
7346
|
+
input.addEventListener("change", () => {
|
|
7347
|
+
void (async () => {
|
|
7348
|
+
try {
|
|
7349
|
+
const files = Array.from(input.files ?? []);
|
|
7350
|
+
const converted = await Promise.all(files.map((file) => this.readSelectedFile(file)));
|
|
7351
|
+
cleanup();
|
|
7352
|
+
finish(input.multiple ? converted : converted[0] ?? null);
|
|
7353
|
+
} catch (error) {
|
|
7354
|
+
cleanup();
|
|
7355
|
+
fail(error);
|
|
7356
|
+
}
|
|
7357
|
+
})();
|
|
7358
|
+
}, { once: true });
|
|
7359
|
+
input.addEventListener("cancel", () => {
|
|
7360
|
+
cleanup();
|
|
7361
|
+
finish(null);
|
|
7362
|
+
}, { once: true });
|
|
7363
|
+
window.addEventListener("focus", handleFocus, true);
|
|
7364
|
+
document.body.append(input);
|
|
7365
|
+
input.click();
|
|
7366
|
+
});
|
|
7367
|
+
return selection;
|
|
7368
|
+
}
|
|
7369
|
+
readSelectedFile(file) {
|
|
7370
|
+
return new Promise((resolve, reject) => {
|
|
7371
|
+
const reader = new FileReader();
|
|
7372
|
+
reader.onerror = () => {
|
|
7373
|
+
reject(reader.error ?? new Error(`Failed to read file ${file.name}`));
|
|
7374
|
+
};
|
|
7375
|
+
reader.onload = () => {
|
|
7376
|
+
const result = typeof reader.result === "string" ? reader.result : "";
|
|
7377
|
+
const [, data = ""] = result.split(",", 2);
|
|
7378
|
+
resolve({
|
|
7379
|
+
name: file.name,
|
|
7380
|
+
mimeType: file.type || "application/octet-stream",
|
|
7381
|
+
size: file.size,
|
|
7382
|
+
data
|
|
7383
|
+
});
|
|
7384
|
+
};
|
|
7385
|
+
reader.readAsDataURL(file);
|
|
7386
|
+
});
|
|
7387
|
+
}
|
|
7388
|
+
async confirmModal(args, _inputValue) {
|
|
7389
|
+
const options = isPlainObject4(args) ? args : {};
|
|
7390
|
+
const caption = typeof options.caption === "string" && options.caption ? options.caption : "Confirm?";
|
|
7391
|
+
const yes = typeof options.yes === "string" && options.yes ? options.yes : "Yes";
|
|
7392
|
+
const no = typeof options.no === "string" && options.no ? options.no : "No";
|
|
7393
|
+
return new Promise((resolve) => {
|
|
7394
|
+
this.activeConfirmModal?.resolve(false);
|
|
7395
|
+
this.activeConfirmModal = {
|
|
7396
|
+
caption,
|
|
7397
|
+
yes,
|
|
7398
|
+
no,
|
|
7399
|
+
resolve
|
|
7400
|
+
};
|
|
7401
|
+
void this.rerenderRoot();
|
|
7402
|
+
});
|
|
7403
|
+
}
|
|
7404
|
+
resolveConfirmModal(confirmed) {
|
|
7405
|
+
const activeModal = this.activeConfirmModal;
|
|
7406
|
+
if (!activeModal) {
|
|
7407
|
+
return;
|
|
7408
|
+
}
|
|
7409
|
+
this.activeConfirmModal = null;
|
|
7410
|
+
activeModal.resolve(confirmed);
|
|
7411
|
+
}
|
|
7127
7412
|
reindexStaticTree() {
|
|
7128
7413
|
this.nodeByKey.clear();
|
|
7129
7414
|
this.nodeById.clear();
|
|
@@ -7178,7 +7463,11 @@ var RuntimeRenderer = class {
|
|
|
7178
7463
|
stateByKey: Array.from(this.stateByKey.entries(), ([key, state]) => [key, deepClone(state)]),
|
|
7179
7464
|
vars: Array.from(this.vars.entries(), ([name, value]) => [name, deepClone(value)]),
|
|
7180
7465
|
activeModalId: this.activeModalId,
|
|
7181
|
-
activeContextMenu: this.activeContextMenu ?
|
|
7466
|
+
activeContextMenu: this.activeContextMenu ? {
|
|
7467
|
+
id: this.activeContextMenu.id,
|
|
7468
|
+
x: this.activeContextMenu.x,
|
|
7469
|
+
y: this.activeContextMenu.y
|
|
7470
|
+
} : null
|
|
7182
7471
|
};
|
|
7183
7472
|
}
|
|
7184
7473
|
indexStaticNodes(node, path) {
|
|
@@ -7688,7 +7977,8 @@ var RuntimeRenderer = class {
|
|
|
7688
7977
|
const env = this.getWidgetRenderEnvironment();
|
|
7689
7978
|
const modalMarkup = this.activeModalId ? renderModalOverlay(env, this.activeModalId) : "";
|
|
7690
7979
|
const menuMarkup = this.activeContextMenu ? renderContextMenuOverlay(env, this.activeContextMenu) : "";
|
|
7691
|
-
|
|
7980
|
+
const confirmMarkup = this.activeConfirmModal ? renderConfirmModalOverlay(env, this.activeConfirmModal) : "";
|
|
7981
|
+
return `${modalMarkup}${menuMarkup}${confirmMarkup}`;
|
|
7692
7982
|
}
|
|
7693
7983
|
updatePointerFromEvent(event) {
|
|
7694
7984
|
this.lastPointer = updatePointerFromMouseEvent(event);
|
|
@@ -7915,6 +8205,7 @@ var RuntimeRenderer = class {
|
|
|
7915
8205
|
nodeByKey: this.nodeByKey,
|
|
7916
8206
|
stateByKey: this.stateByKey,
|
|
7917
8207
|
getLastPointer: () => this.lastPointer,
|
|
8208
|
+
getActiveContextMenu: () => this.activeContextMenu,
|
|
7918
8209
|
setActiveContextMenu: (menu) => {
|
|
7919
8210
|
this.activeContextMenu = menu;
|
|
7920
8211
|
},
|
|
@@ -7924,6 +8215,7 @@ var RuntimeRenderer = class {
|
|
|
7924
8215
|
updateSplitterDrag: (clientPosition) => this.updateSplitterDrag(clientPosition),
|
|
7925
8216
|
stopSplitterDrag: () => this.stopSplitterDrag(),
|
|
7926
8217
|
hasSplitterDrag: () => this.splitterDragState !== null,
|
|
8218
|
+
resolveConfirmModal: (confirmed) => this.resolveConfirmModal(confirmed),
|
|
7927
8219
|
closeModal: (modalId) => this.closeModal(modalId),
|
|
7928
8220
|
rerenderRoot: () => this.rerenderRoot(),
|
|
7929
8221
|
redispatchUnderlyingClick: (x, y) => this.redispatchUnderlyingClick(x, y),
|
|
@@ -34,6 +34,9 @@ type ActionRuntimeOptions = {
|
|
|
34
34
|
focusWidget: (reference: string) => void;
|
|
35
35
|
playAudio: (value: unknown) => Promise<void>;
|
|
36
36
|
stopPlaying: () => Promise<void>;
|
|
37
|
+
copyToClipboard: (value: unknown) => Promise<void>;
|
|
38
|
+
selectFile: (options: unknown) => Promise<unknown>;
|
|
39
|
+
confirmModal: (options: unknown, inputValue: unknown) => Promise<boolean>;
|
|
37
40
|
startRecording: () => Promise<void>;
|
|
38
41
|
stopRecording: () => Promise<void>;
|
|
39
42
|
startListening: () => Promise<void>;
|
|
@@ -53,6 +56,8 @@ type ActionRuntimeOptions = {
|
|
|
53
56
|
id: string;
|
|
54
57
|
x: number;
|
|
55
58
|
y: number;
|
|
59
|
+
currentValue?: unknown;
|
|
60
|
+
openPath?: number[];
|
|
56
61
|
} | null) => void;
|
|
57
62
|
setActiveModalId: (modalId: string | null) => void;
|
|
58
63
|
closeModal: (modalId?: string | null) => void;
|
|
@@ -79,6 +84,9 @@ export declare class ActionRuntime {
|
|
|
79
84
|
private readonly focusWidget;
|
|
80
85
|
private readonly playAudio;
|
|
81
86
|
private readonly stopPlaying;
|
|
87
|
+
private readonly copyToClipboard;
|
|
88
|
+
private readonly selectFile;
|
|
89
|
+
private readonly confirmModal;
|
|
82
90
|
private readonly startRecording;
|
|
83
91
|
private readonly stopRecording;
|
|
84
92
|
private readonly startListening;
|
|
@@ -106,5 +114,6 @@ export declare class ActionRuntime {
|
|
|
106
114
|
private getCurrentListState;
|
|
107
115
|
private cloneValue;
|
|
108
116
|
private stringifyValue;
|
|
117
|
+
private isCurrentScopedReference;
|
|
109
118
|
}
|
|
110
119
|
export {};
|
package/dist/lib/dom-state.d.ts
CHANGED
|
@@ -21,10 +21,14 @@ export declare function adjustContextMenuPosition(root: HTMLElement, activeConte
|
|
|
21
21
|
id: string;
|
|
22
22
|
x: number;
|
|
23
23
|
y: number;
|
|
24
|
+
currentValue?: unknown;
|
|
25
|
+
openPath?: number[];
|
|
24
26
|
} | null): {
|
|
25
27
|
id: string;
|
|
26
28
|
x: number;
|
|
27
29
|
y: number;
|
|
30
|
+
currentValue?: unknown;
|
|
31
|
+
openPath?: number[];
|
|
28
32
|
} | null;
|
|
29
33
|
export declare function updatePointerFromMouseEvent(event: MouseEvent): {
|
|
30
34
|
x: number;
|
|
@@ -1,14 +1,20 @@
|
|
|
1
1
|
import type { ActionDefinition, BaseNode } from '../types.js';
|
|
2
2
|
import type { WidgetRenderEnvironment } from './context.js';
|
|
3
|
+
export type ContextMenuItem = {
|
|
4
|
+
name: string;
|
|
5
|
+
actions?: ActionDefinition[];
|
|
6
|
+
items?: ContextMenuItem[];
|
|
7
|
+
};
|
|
3
8
|
export type ContextMenuNode = BaseNode & {
|
|
4
9
|
widget: 'context-menu';
|
|
5
|
-
items?:
|
|
6
|
-
name: string;
|
|
7
|
-
actions?: ActionDefinition[];
|
|
8
|
-
}>;
|
|
10
|
+
items?: ContextMenuItem[];
|
|
9
11
|
};
|
|
10
|
-
export
|
|
12
|
+
export type ActiveContextMenuState = {
|
|
11
13
|
id: string;
|
|
12
14
|
x: number;
|
|
13
15
|
y: number;
|
|
14
|
-
|
|
16
|
+
currentValue?: unknown;
|
|
17
|
+
openPath?: number[];
|
|
18
|
+
};
|
|
19
|
+
export declare function getContextMenuItemAtPath(items: ContextMenuItem[] | undefined, path: number[]): ContextMenuItem | null;
|
|
20
|
+
export declare function renderContextMenuOverlay(env: WidgetRenderEnvironment, menu: ActiveContextMenuState): string;
|
|
@@ -5,7 +5,7 @@ import type { CheckboxNode } from './checkbox.js';
|
|
|
5
5
|
import type { ComboboxNode } from './combobox.js';
|
|
6
6
|
import type { ConditionalContainerNode } from './conditional-container.js';
|
|
7
7
|
import type { ContainerCell, ContainerLayoutNode } from './container-layout.js';
|
|
8
|
-
import type { ContextMenuNode } from './context-menu.js';
|
|
8
|
+
import type { ActiveContextMenuState, ContextMenuNode } from './context-menu.js';
|
|
9
9
|
import type { GridViewNode } from './grid-view.js';
|
|
10
10
|
import type { ImageNode } from './image.js';
|
|
11
11
|
import type { LinkNode } from './link.js';
|
|
@@ -23,11 +23,7 @@ import type { TabsNode } from './tabs.js';
|
|
|
23
23
|
import type { TextareaNode } from './textarea.js';
|
|
24
24
|
import type { UiReferenceNode } from './ui-reference.js';
|
|
25
25
|
export type WidgetRenderEnvironment = {
|
|
26
|
-
readonly activeContextMenu:
|
|
27
|
-
id: string;
|
|
28
|
-
x: number;
|
|
29
|
-
y: number;
|
|
30
|
-
} | null;
|
|
26
|
+
readonly activeContextMenu: ActiveContextMenuState | null;
|
|
31
27
|
readonly activeModalId: string | null;
|
|
32
28
|
readonly nodeById: Map<string, DescriptionNode>;
|
|
33
29
|
escapeHtml: (value: unknown) => string;
|
|
@@ -7,10 +7,19 @@ type DelegationEnvironment = {
|
|
|
7
7
|
x: number;
|
|
8
8
|
y: number;
|
|
9
9
|
};
|
|
10
|
+
getActiveContextMenu: () => {
|
|
11
|
+
id: string;
|
|
12
|
+
x: number;
|
|
13
|
+
y: number;
|
|
14
|
+
currentValue?: unknown;
|
|
15
|
+
openPath?: number[];
|
|
16
|
+
} | null;
|
|
10
17
|
setActiveContextMenu: (menu: {
|
|
11
18
|
id: string;
|
|
12
19
|
x: number;
|
|
13
20
|
y: number;
|
|
21
|
+
currentValue?: unknown;
|
|
22
|
+
openPath?: number[];
|
|
14
23
|
} | null) => void;
|
|
15
24
|
isWidgetEnabled: (node: DescriptionNode, key: string) => boolean;
|
|
16
25
|
startSplitterDrag: (layoutKey: string, axis: 'horizontal' | 'vertical', splitterIndex: number, startPointer: number) => void;
|
|
@@ -18,6 +27,7 @@ type DelegationEnvironment = {
|
|
|
18
27
|
updateSplitterDrag: (clientPosition: number) => void;
|
|
19
28
|
stopSplitterDrag: () => void;
|
|
20
29
|
hasSplitterDrag: () => boolean;
|
|
30
|
+
resolveConfirmModal: (confirmed: boolean) => void;
|
|
21
31
|
closeModal: (modalId: string | null) => void;
|
|
22
32
|
rerenderRoot: () => Promise<void>;
|
|
23
33
|
redispatchUnderlyingClick: (clientX: number, clientY: number) => void;
|
package/package.json
CHANGED
package/vjt-styles.css
CHANGED
|
@@ -685,12 +685,21 @@ body {
|
|
|
685
685
|
}
|
|
686
686
|
|
|
687
687
|
.vjt-modal-backdrop,
|
|
688
|
-
.vjt-context-menu-backdrop
|
|
688
|
+
.vjt-context-menu-backdrop,
|
|
689
|
+
.vjt-confirm-backdrop {
|
|
689
690
|
position: fixed;
|
|
690
691
|
inset: 0;
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
.vjt-modal-backdrop,
|
|
695
|
+
.vjt-context-menu-backdrop {
|
|
691
696
|
z-index: 1000;
|
|
692
697
|
}
|
|
693
698
|
|
|
699
|
+
.vjt-confirm-backdrop {
|
|
700
|
+
z-index: 1100;
|
|
701
|
+
}
|
|
702
|
+
|
|
694
703
|
.vjt-modal-backdrop {
|
|
695
704
|
display: flex;
|
|
696
705
|
align-items: center;
|
|
@@ -759,16 +768,20 @@ body {
|
|
|
759
768
|
}
|
|
760
769
|
|
|
761
770
|
.vjt-context-menu {
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
771
|
+
position: fixed;
|
|
772
|
+
box-sizing: border-box;
|
|
773
|
+
width: 220px;
|
|
774
|
+
padding: 8px;
|
|
775
|
+
border-radius: 14px;
|
|
766
776
|
border: 1px solid var(--vjt-border);
|
|
767
777
|
background: var(--vjt-surface);
|
|
768
778
|
box-shadow: var(--vjt-shadow);
|
|
769
779
|
}
|
|
770
780
|
|
|
771
781
|
.vjt-context-menu-item {
|
|
782
|
+
display: flex;
|
|
783
|
+
align-items: center;
|
|
784
|
+
justify-content: space-between;
|
|
772
785
|
width: 100%;
|
|
773
786
|
padding: 10px 12px;
|
|
774
787
|
text-align: left;
|
|
@@ -781,3 +794,48 @@ body {
|
|
|
781
794
|
.vjt-context-menu-item:hover {
|
|
782
795
|
background: color-mix(in srgb, var(--vjt-accent) 14%, transparent);
|
|
783
796
|
}
|
|
797
|
+
|
|
798
|
+
.vjt-context-menu-item--parent {
|
|
799
|
+
font-weight: 500;
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
.vjt-context-menu-chevron {
|
|
803
|
+
margin-left: 16px;
|
|
804
|
+
opacity: 0.7;
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
.vjt-confirm-backdrop {
|
|
808
|
+
display: flex;
|
|
809
|
+
align-items: center;
|
|
810
|
+
justify-content: center;
|
|
811
|
+
background: rgba(7, 10, 15, 0.42);
|
|
812
|
+
padding: 24px;
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
.vjt-confirm-window {
|
|
816
|
+
width: min(420px, calc(100vw - 32px));
|
|
817
|
+
display: flex;
|
|
818
|
+
flex-direction: column;
|
|
819
|
+
gap: 18px;
|
|
820
|
+
padding: 22px;
|
|
821
|
+
border-radius: 18px;
|
|
822
|
+
border: 1px solid var(--vjt-border);
|
|
823
|
+
background: var(--vjt-modal-surface);
|
|
824
|
+
color: var(--vjt-text);
|
|
825
|
+
box-shadow: var(--vjt-shadow);
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
.vjt-confirm-caption {
|
|
829
|
+
line-height: 1.45;
|
|
830
|
+
white-space: pre-wrap;
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
.vjt-confirm-actions {
|
|
834
|
+
display: flex;
|
|
835
|
+
justify-content: flex-end;
|
|
836
|
+
gap: 10px;
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
.vjt-confirm-actions .vjt-button {
|
|
840
|
+
min-width: 120px;
|
|
841
|
+
}
|