Compare commits

..

6 Commits

Author SHA1 Message Date
Will Miao
dd5b213adc fix(ui): make autocomplete text widget scrollable in Nodes 2.0 mode
In Vue/Node 2.0 mode, the AutocompleteTextWidget's textarea wheel events were intercepted by TransformPane @wheel.capture before reaching the @wheel handler, causing canvas zoom instead of text scrolling.

- Add lm-wheel-scrollable class in Vue mode to hook into the window capture-phase handler (enableListWheelScroll) which scrolls the textarea manually before TransformPane can react.
- Add maxHeight prop and container max-height for Lora Loader/Stacker/WanVideo nodes (modelType === 'loras'), matching canvas mode's height cap. Prompt/Text nodes remain uncapped.
2026-06-06 08:12:09 +08:00
Will Miao
d9ee9b3155 fix(utils): catch MemoryError in read_safetensors_metadata for non-safetensors files 2026-06-06 07:35:36 +08:00
pixelpaws
01dac57c35 Merge pull request #959 from id-fa/fix/lora-loader-list-scroll-nodes2
fix(ui): make Lora Loader list scrollable in Nodes 2.0 mode
2026-06-06 07:33:19 +08:00
id-fa
7f92d09239 fix(ui): make Lora Loader list scrollable in Nodes 2.0 mode
In Nodes 2.0 / Vue node mode the Lora Loader list could not be capped
and the node grew to show every row, unlike classic mode which fixes the
list area to 12 rows. The Vue layout engine measures the rendered DOM, so
CSS variables and computeLayoutSize alone were ignored.

- Physically cap the container via max-height so the rendered element is
  bounded to the 12-row height; extra rows scroll (overflow: auto).
- Report the capped height through computeSize / computeLayoutSize /
  getHeight / getMinHeight so the node background matches the list.
- Add enableListWheelScroll: a window capture-phase wheel hook that scrolls
  the hovered list instead of letting ComfyUI zoom the canvas, which fires
  on the document/canvas in capture and beat a container-level listener.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 20:29:01 +09:00
Will Miao
62f9e3f44a fix(scripts): use platformdirs for cross-platform settings path resolution
Both restore_suffixed_filenames.py and migrate_legacy_metadata.py
hardcoded Path.home() / '.config' / APP_NAME for finding settings.json,
which only works on Linux. On Windows this resolves to the wrong path
(~/.config/ instead of %LOCALAPPDATA%).

Replace the hand-rolled fallback with platformdirs.user_config_dir(),
which correctly resolves to the OS-appropriate config directory on all
platforms (Windows: %%LOCALAPPDATA%%, macOS: ~/Library/Application Support,
Linux: ~/.config). The portable mode check (settings.json in repo root
with use_portable_settings: true) is preserved unchanged.
2026-06-04 07:17:53 +08:00
willmiao
e55895786d docs: auto-update supporters list in README 2026-06-03 14:30:44 +00:00
11 changed files with 148 additions and 48 deletions

File diff suppressed because one or more lines are too long

View File

@@ -81,7 +81,7 @@ def read_safetensors_metadata(file_path: str) -> dict[str, Any]:
return {} return {}
header = json.loads(header_bytes.decode("utf-8")) header = json.loads(header_bytes.decode("utf-8"))
return header.get("__metadata__", {}) return header.get("__metadata__", {})
except (OSError, json.JSONDecodeError, UnicodeDecodeError, struct.error): except (OSError, json.JSONDecodeError, UnicodeDecodeError, struct.error, MemoryError, Exception):
return {} return {}

View File

@@ -34,6 +34,8 @@ import sys
from pathlib import Path from pathlib import Path
from typing import Any from typing import Any
from platformdirs import user_config_dir
logging.basicConfig( logging.basicConfig(
level=logging.INFO, level=logging.INFO,
format="%(asctime)s - %(levelname)s - %(message)s", format="%(asctime)s - %(levelname)s - %(message)s",
@@ -53,10 +55,7 @@ def resolve_settings_path() -> Path:
if isinstance(payload, dict) and payload.get("use_portable_settings") is True: if isinstance(payload, dict) and payload.get("use_portable_settings") is True:
return portable return portable
config_home = os.environ.get("XDG_CONFIG_HOME") return Path(user_config_dir(APP_NAME, appauthor=False)) / "settings.json"
if config_home:
return Path(config_home).expanduser() / APP_NAME / "settings.json"
return Path.home() / ".config" / APP_NAME / "settings.json"
def load_json(path: Path) -> dict[str, Any]: def load_json(path: Path) -> dict[str, Any]:

View File

@@ -39,6 +39,8 @@ import sys
from pathlib import Path from pathlib import Path
from typing import Any from typing import Any
from platformdirs import user_config_dir
logging.basicConfig( logging.basicConfig(
level=logging.INFO, level=logging.INFO,
format="%(message)s", format="%(message)s",
@@ -68,10 +70,7 @@ def resolve_settings_path() -> Path:
if isinstance(payload, dict) and payload.get("use_portable_settings") is True: if isinstance(payload, dict) and payload.get("use_portable_settings") is True:
return portable return portable
config_home = os.environ.get("XDG_CONFIG_HOME") return Path(user_config_dir(APP_NAME, appauthor=False)) / "settings.json"
if config_home:
return Path(config_home).expanduser() / APP_NAME / "settings.json"
return Path.home() / ".config" / APP_NAME / "settings.json"
def _load_json(path: Path) -> dict[str, Any]: def _load_json(path: Path) -> dict[str, Any]:

View File

@@ -5,7 +5,8 @@
ref="textareaRef" ref="textareaRef"
:placeholder="placeholder" :placeholder="placeholder"
:spellcheck="spellcheck ?? false" :spellcheck="spellcheck ?? false"
:class="['text-input', { 'vue-dom-mode': isVueDomMode }]" :class="['text-input', { 'vue-dom-mode': isVueDomMode, 'lm-wheel-scrollable': isVueDomMode }]"
:style="maxHeight && isVueDomMode ? { maxHeight: maxHeight + 'px' } : undefined"
@input="onInput" @input="onInput"
@wheel="onWheel" @wheel="onWheel"
/> />
@@ -47,6 +48,7 @@ const props = defineProps<{
placeholder?: string placeholder?: string
showPreview?: boolean showPreview?: boolean
spellcheck?: boolean spellcheck?: boolean
maxHeight?: number
}>() }>()
// Reactive ref for Vue DOM mode // Reactive ref for Vue DOM mode

View File

@@ -655,13 +655,16 @@ function createAutocompleteTextWidgetFactory(
// Get spellcheck setting from ComfyUI settings (default: false) // Get spellcheck setting from ComfyUI settings (default: false)
const spellcheck = app.ui?.settings?.getSettingValue?.('Comfy.TextareaWidget.Spellcheck') ?? false const spellcheck = app.ui?.settings?.getSettingValue?.('Comfy.TextareaWidget.Spellcheck') ?? false
const maxHeight = modelType === 'loras' ? AUTOCOMPLETE_TEXT_WIDGET_MAX_HEIGHT : undefined
const vueApp = createApp(AutocompleteTextWidget, { const vueApp = createApp(AutocompleteTextWidget, {
widget, widget,
node, node,
modelType, modelType,
placeholder: inputOptions.placeholder || widgetName, placeholder: inputOptions.placeholder || widgetName,
showPreview: true, showPreview: true,
spellcheck spellcheck,
maxHeight
}) })
vueApp.use(PrimeVue, { vueApp.use(PrimeVue, {
@@ -673,6 +676,10 @@ function createAutocompleteTextWidgetFactory(
const appKey = instanceId const appKey = instanceId
vueApps.set(appKey, vueApp) vueApps.set(appKey, vueApp)
if (maxHeight) {
container.style.maxHeight = `${maxHeight}px`
}
widget.onRemove = createVueWidgetCleanup(vueApp, () => { widget.onRemove = createVueWidgetCleanup(vueApp, () => {
vueApps.delete(appKey) vueApps.delete(appKey)
}) })

View File

@@ -11,7 +11,7 @@ import {
EMPTY_CONTAINER_HEIGHT EMPTY_CONTAINER_HEIGHT
} from "./loras_widget_utils.js"; } from "./loras_widget_utils.js";
import { initDrag, createContextMenu, initHeaderDrag, initReorderDrag, handleKeyboardNavigation } from "./loras_widget_events.js"; import { initDrag, createContextMenu, initHeaderDrag, initReorderDrag, handleKeyboardNavigation } from "./loras_widget_events.js";
import { forwardMiddleMouseToCanvas, forwardWheelToCanvas } from "./utils.js"; import { forwardMiddleMouseToCanvas, forwardWheelToCanvas, enableListWheelScroll } from "./utils.js";
import { PreviewTooltip } from "./preview_tooltip.js"; import { PreviewTooltip } from "./preview_tooltip.js";
import { ensureLmStyles } from "./lm_styles_loader.js"; import { ensureLmStyles } from "./lm_styles_loader.js";
import { getStrengthStepPreference } from "./settings.js"; import { getStrengthStepPreference } from "./settings.js";
@@ -24,11 +24,18 @@ export function addLorasWidget(node, name, opts, callback) {
container.className = "lm-loras-container"; container.className = "lm-loras-container";
forwardMiddleMouseToCanvas(container); forwardMiddleMouseToCanvas(container);
// Capture-phase handler: scroll the list with the wheel when it overflows.
// Falls through to forwardWheelToCanvas (canvas zoom) when the list is short.
enableListWheelScroll(container);
forwardWheelToCanvas(container); forwardWheelToCanvas(container);
// Set initial height using CSS variables approach // Set initial height using CSS variables approach
const defaultHeight = 200; const defaultHeight = 200;
// Content height (capped at 12 rows by renderLoras), kept up to date and used
// to fix the widget/node height in both Canvas and Nodes 2.0 (Vue) modes.
let currentContentHeight = defaultHeight;
// Check if this is a randomizer node (lock button instead of drag handle) // Check if this is a randomizer node (lock button instead of drag handle)
const isRandomizerNode = opts?.isRandomizerNode === true; const isRandomizerNode = opts?.isRandomizerNode === true;
@@ -198,7 +205,7 @@ export function addLorasWidget(node, name, opts, callback) {
container.appendChild(emptyMessage); container.appendChild(emptyMessage);
// Set fixed height for empty state // Set fixed height for empty state
updateWidgetHeight(container, EMPTY_CONTAINER_HEIGHT, defaultHeight, node); currentContentHeight = updateWidgetHeight(container, EMPTY_CONTAINER_HEIGHT, defaultHeight, node);
return; return;
} }
@@ -645,7 +652,7 @@ export function addLorasWidget(node, name, opts, callback) {
// Calculate height based on number of loras and fixed sizes // Calculate height based on number of loras and fixed sizes
const calculatedHeight = CONTAINER_PADDING + HEADER_HEIGHT + (Math.min(totalVisibleEntries, 12) * LORA_ENTRY_HEIGHT); const calculatedHeight = CONTAINER_PADDING + HEADER_HEIGHT + (Math.min(totalVisibleEntries, 12) * LORA_ENTRY_HEIGHT);
updateWidgetHeight(container, calculatedHeight, defaultHeight, node); currentContentHeight = updateWidgetHeight(container, calculatedHeight, defaultHeight, node);
// After all LoRA elements are created, apply selection state as the last step // After all LoRA elements are created, apply selection state as the last step
// This ensures the selection state is not overwritten // This ensures the selection state is not overwritten
@@ -727,12 +734,31 @@ export function addLorasWidget(node, name, opts, callback) {
widgetValue = updatedValue; widgetValue = updatedValue;
renderLoras(widgetValue, widget); renderLoras(widgetValue, widget);
}, },
// The list area is capped at 12 rows (see calculatedHeight); beyond that the
// container scrolls. Report that capped height as both the min and preferred
// size so the node height stays fixed to the list, matching Canvas mode.
getMinHeight: () => currentContentHeight,
getHeight: () => currentContentHeight,
hideOnZoom: true, hideOnZoom: true,
selectOn: ['click', 'focus'] selectOn: ['click', 'focus']
}); });
widget.value = defaultValue; widget.value = defaultValue;
// Canonical LiteGraph sizing hook (Canvas mode): fix the widget to the capped
// content height. Rows beyond the 12-row cap scroll inside the container.
widget.computeSize = (width) => [width, currentContentHeight];
// Nodes 2.0 / Vue mode reads computeLayoutSize for the node's size. Pin both
// the min and max to the capped content height so the list area is fixed to
// 12 rows (scrolling beyond), matching Canvas mode, instead of the layout
// engine measuring the full DOM and locking the node fully expanded.
widget.computeLayoutSize = () => ({
minHeight: currentContentHeight,
maxHeight: currentContentHeight,
minWidth: 400,
});
widget.callback = callback; widget.callback = callback;
widget.onRemove = () => { widget.onRemove = () => {

View File

@@ -18,21 +18,28 @@ export function formatLoraValue(loras) {
return loras; return loras;
} }
// Function to update widget height consistently // Resolve the capped (12-row) height of the widget and physically cap the
// container so the list area never grows past it.
// `height` is the raw content height (already capped at 12 rows by the caller);
// the result never drops below defaultHeight.
// The max-height is the reliable lever: Nodes 2.0 / Vue mode measures the
// rendered DOM to size the node, so without an actual height cap on the element
// the list always shows every row. max-height bounds the element regardless of
// what the layout engine measures, and the overflow makes the extra rows scroll.
// Returns the resolved height so callers can also report it to ComfyUI.
export function updateWidgetHeight(container, height, defaultHeight, node) { export function updateWidgetHeight(container, height, defaultHeight, node) {
// Ensure minimum height const cappedHeight = Math.max(defaultHeight, height);
const finalHeight = Math.max(defaultHeight, height);
// Update CSS variables container.style.maxHeight = `${cappedHeight}px`;
container.style.setProperty('--comfy-widget-min-height', `${finalHeight}px`);
container.style.setProperty('--comfy-widget-height', `${finalHeight}px`);
// Force node to update size after a short delay to ensure DOM is updated // Force node to redraw after a short delay to ensure the DOM is updated.
if (node) { if (node) {
setTimeout(() => { setTimeout(() => {
node.setDirtyCanvas(true, true); node.setDirtyCanvas(true, true);
}, 10); }, 10);
} }
return cappedHeight;
} }
// Determine if clip entry should be shown - now based on expanded property or initial diff values // Determine if clip entry should be shown - now based on expanded property or initial diff values

View File

@@ -784,6 +784,59 @@ export function forwardWheelToCanvas(container, options = {}) {
}, { passive: false }); }, { passive: false });
} }
// Marks elements whose wheel scrolling must win over the canvas zoom.
const LIST_WHEEL_SCROLL_CLASS = 'lm-wheel-scrollable';
let listWheelScrollHookInstalled = false;
/**
* Keep vertical wheel scrolling inside a scrollable widget container instead of
* letting ComfyUI zoom the canvas.
*
* In Nodes 2.0 / Vue mode ComfyUI's wheel→zoom handler runs on the document /
* canvas in the capture phase, which is *outer* than the widget, so a listener
* on the container (even in capture) fires too late. The reliable place to win
* is a single hook on `window` in the capture phase — the very first step of
* event dispatch. When the wheel is over a marked, scrollable element we scroll
* it manually and fully consume the event; otherwise we leave it alone so canvas
* zoom keeps working.
* @param {HTMLElement} container - The scrollable element (overflow: auto)
*/
export function enableListWheelScroll(container) {
if (!container) return;
container.classList.add(LIST_WHEEL_SCROLL_CLASS);
if (listWheelScrollHookInstalled) return;
listWheelScrollHookInstalled = true;
window.addEventListener('wheel', (event) => {
// Let pinch/zoom and horizontal gestures fall through to the canvas.
if (event.ctrlKey) return;
if (Math.abs(event.deltaX) > Math.abs(event.deltaY)) return;
const target = event.target;
if (!target || typeof target.closest !== 'function') return;
const el = target.closest(`.${LIST_WHEEL_SCROLL_CLASS}`);
if (!el) return;
const canScrollY = el.scrollHeight > el.clientHeight + 1;
if (!canScrollY) return; // Nothing to scroll → allow canvas zoom.
// Translate the wheel delta to pixels (line / page modes → approx px).
const unit = event.deltaMode === 1
? 16
: event.deltaMode === 2
? el.clientHeight
: 1;
el.scrollTop += event.deltaY * unit;
// Consume the event so neither ComfyUI's zoom nor forwardWheelToCanvas react.
event.preventDefault();
event.stopPropagation();
event.stopImmediatePropagation();
}, { capture: true, passive: false });
}
// Get connected Lora Pool node from pool_config input // Get connected Lora Pool node from pool_config input
export function getConnectedPoolConfigNode(node) { export function getConnectedPoolConfigNode(node) {
if (!node?.inputs) { if (!node?.inputs) {

View File

@@ -2118,14 +2118,14 @@ to { transform: rotate(360deg);
padding: 20px 0; padding: 20px 0;
} }
.autocomplete-text-widget[data-v-5514bf46] { .autocomplete-text-widget[data-v-1c610e5d] {
background: transparent; background: transparent;
height: 100%; height: 100%;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
box-sizing: border-box; box-sizing: border-box;
} }
.input-wrapper[data-v-5514bf46] { .input-wrapper[data-v-1c610e5d] {
position: relative; position: relative;
flex: 1; flex: 1;
display: flex; display: flex;
@@ -2133,7 +2133,7 @@ to { transform: rotate(360deg);
} }
/* Canvas mode styles (default) - matches built-in comfy-multiline-input */ /* Canvas mode styles (default) - matches built-in comfy-multiline-input */
.text-input[data-v-5514bf46] { .text-input[data-v-1c610e5d] {
flex: 1; flex: 1;
width: 100%; width: 100%;
background-color: var(--comfy-input-bg, #222); background-color: var(--comfy-input-bg, #222);
@@ -2150,7 +2150,7 @@ to { transform: rotate(360deg);
} }
/* Vue DOM mode styles - matches built-in p-textarea in Vue DOM mode */ /* Vue DOM mode styles - matches built-in p-textarea in Vue DOM mode */
.text-input.vue-dom-mode[data-v-5514bf46] { .text-input.vue-dom-mode[data-v-1c610e5d] {
background-color: var(--color-charcoal-400, #313235); background-color: var(--color-charcoal-400, #313235);
color: #fff; color: #fff;
padding: 8px 12px 30px 12px; /* Reserve bottom space for clear button */ padding: 8px 12px 30px 12px; /* Reserve bottom space for clear button */
@@ -2159,12 +2159,12 @@ to { transform: rotate(360deg);
font-size: 12px; font-size: 12px;
font-family: inherit; font-family: inherit;
} }
.text-input[data-v-5514bf46]:focus { .text-input[data-v-1c610e5d]:focus {
outline: none; outline: none;
} }
/* Clear button styles */ /* Clear button styles */
.clear-button[data-v-5514bf46] { .clear-button[data-v-1c610e5d] {
position: absolute; position: absolute;
right: 6px; right: 6px;
bottom: 6px; /* Changed from top to bottom */ bottom: 6px; /* Changed from top to bottom */
@@ -2187,31 +2187,31 @@ to { transform: rotate(360deg);
} }
/* Show clear button when hovering over input wrapper */ /* Show clear button when hovering over input wrapper */
.input-wrapper:hover .clear-button[data-v-5514bf46] { .input-wrapper:hover .clear-button[data-v-1c610e5d] {
opacity: 0.7; opacity: 0.7;
pointer-events: auto; pointer-events: auto;
} }
.clear-button[data-v-5514bf46]:hover { .clear-button[data-v-1c610e5d]:hover {
opacity: 1; opacity: 1;
background: rgba(255, 100, 100, 0.8); background: rgba(255, 100, 100, 0.8);
} }
.clear-button svg[data-v-5514bf46] { .clear-button svg[data-v-1c610e5d] {
width: 12px; width: 12px;
height: 12px; height: 12px;
} }
/* Vue DOM mode adjustments for clear button */ /* Vue DOM mode adjustments for clear button */
.text-input.vue-dom-mode ~ .clear-button[data-v-5514bf46] { .text-input.vue-dom-mode ~ .clear-button[data-v-1c610e5d] {
right: 8px; right: 8px;
bottom: 10px; /* Changed from top to bottom, adjusted for Vue DOM padding */ bottom: 10px; /* Changed from top to bottom, adjusted for Vue DOM padding */
width: 20px; width: 20px;
height: 20px; height: 20px;
background: rgba(107, 114, 128, 0.6); background: rgba(107, 114, 128, 0.6);
} }
.text-input.vue-dom-mode ~ .clear-button[data-v-5514bf46]:hover { .text-input.vue-dom-mode ~ .clear-button[data-v-1c610e5d]:hover {
background: oklch(62% 0.18 25); background: oklch(62% 0.18 25);
} }
.text-input.vue-dom-mode ~ .clear-button svg[data-v-5514bf46] { .text-input.vue-dom-mode ~ .clear-button svg[data-v-1c610e5d] {
width: 14px; width: 14px;
height: 14px; height: 14px;
}`)); }`));
@@ -14783,7 +14783,8 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
modelType: {}, modelType: {},
placeholder: {}, placeholder: {},
showPreview: { type: Boolean }, showPreview: { type: Boolean },
spellcheck: { type: Boolean } spellcheck: { type: Boolean },
maxHeight: {}
}, },
setup(__props) { setup(__props) {
const props = __props; const props = __props;
@@ -14913,10 +14914,11 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
ref: textareaRef, ref: textareaRef,
placeholder: __props.placeholder, placeholder: __props.placeholder,
spellcheck: __props.spellcheck ?? false, spellcheck: __props.spellcheck ?? false,
class: normalizeClass(["text-input", { "vue-dom-mode": isVueDomMode.value }]), class: normalizeClass(["text-input", { "vue-dom-mode": isVueDomMode.value, "lm-wheel-scrollable": isVueDomMode.value }]),
style: normalizeStyle(__props.maxHeight && isVueDomMode.value ? { maxHeight: __props.maxHeight + "px" } : void 0),
onInput, onInput,
onWheel onWheel
}, null, 42, _hoisted_3), }, null, 46, _hoisted_3),
showClearButton.value ? (openBlock(), createElementBlock("button", { showClearButton.value ? (openBlock(), createElementBlock("button", {
key: 0, key: 0,
type: "button", type: "button",
@@ -14949,7 +14951,7 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
}; };
} }
}); });
const AutocompleteTextWidget = /* @__PURE__ */ _export_sfc(_sfc_main, [["__scopeId", "data-v-5514bf46"]]); const AutocompleteTextWidget = /* @__PURE__ */ _export_sfc(_sfc_main, [["__scopeId", "data-v-1c610e5d"]]);
function createVueWidgetCleanup(vueApp, onCleanup) { function createVueWidgetCleanup(vueApp, onCleanup) {
let didUnmount = false; let didUnmount = false;
return () => { return () => {
@@ -15799,13 +15801,15 @@ function createAutocompleteTextWidgetFactory(node, widgetName, modelType, inputO
); );
widget.metadataWidget = metadataWidget; widget.metadataWidget = metadataWidget;
const spellcheck = ((_c = (_b = (_a2 = app$1.ui) == null ? void 0 : _a2.settings) == null ? void 0 : _b.getSettingValue) == null ? void 0 : _c.call(_b, "Comfy.TextareaWidget.Spellcheck")) ?? false; const spellcheck = ((_c = (_b = (_a2 = app$1.ui) == null ? void 0 : _a2.settings) == null ? void 0 : _b.getSettingValue) == null ? void 0 : _c.call(_b, "Comfy.TextareaWidget.Spellcheck")) ?? false;
const maxHeight = modelType === "loras" ? AUTOCOMPLETE_TEXT_WIDGET_MAX_HEIGHT : void 0;
const vueApp = createApp(AutocompleteTextWidget, { const vueApp = createApp(AutocompleteTextWidget, {
widget, widget,
node, node,
modelType, modelType,
placeholder: inputOptions.placeholder || widgetName, placeholder: inputOptions.placeholder || widgetName,
showPreview: true, showPreview: true,
spellcheck spellcheck,
maxHeight
}); });
vueApp.use(PrimeVue, { vueApp.use(PrimeVue, {
unstyled: true, unstyled: true,
@@ -15814,6 +15818,9 @@ function createAutocompleteTextWidgetFactory(node, widgetName, modelType, inputO
vueApp.mount(container); vueApp.mount(container);
const appKey = instanceId; const appKey = instanceId;
vueApps.set(appKey, vueApp); vueApps.set(appKey, vueApp);
if (maxHeight) {
container.style.maxHeight = `${maxHeight}px`;
}
widget.onRemove = createVueWidgetCleanup(vueApp, () => { widget.onRemove = createVueWidgetCleanup(vueApp, () => {
vueApps.delete(appKey); vueApps.delete(appKey);
}); });

File diff suppressed because one or more lines are too long