- .widget.selected {
- background: var(--color-yellow-200);
+ .widget {
+ height: 100%;
+
+ &.selected {
+ background: var(--color-yellow-200);
+ }
}
.container.selected {
background: var(--color-yellow-400);
diff --git a/src/lib/defaultGraph.ts b/src/lib/defaultGraph.ts
index 50aa856..c17c3f6 100644
--- a/src/lib/defaultGraph.ts
+++ b/src/lib/defaultGraph.ts
@@ -4560,7 +4560,7 @@ const defaultGraph: SerializedAppState = {
showTitle: false,
direction: "horizontal",
classes: "",
- blockVariant: "block",
+ containerVariant: "block",
hidden: false,
flexGrow: 100,
disabled: false
@@ -4580,7 +4580,7 @@ const defaultGraph: SerializedAppState = {
showTitle: false,
direction: "vertical",
classes: "",
- blockVariant: "block",
+ containerVariant: "block",
hidden: false,
flexGrow: 100,
disabled: false
@@ -4605,7 +4605,7 @@ const defaultGraph: SerializedAppState = {
showTitle: false,
direction: "vertical",
classes: "",
- blockVariant: "block",
+ containerVariant: "block",
hidden: false,
flexGrow: 100,
disabled: false
@@ -4736,7 +4736,7 @@ const defaultGraph: SerializedAppState = {
showTitle: true,
direction: "vertical",
classes: "",
- blockVariant: "block",
+ containerVariant: "block",
hidden: false,
flexGrow: 100,
disabled: false
@@ -4776,7 +4776,7 @@ const defaultGraph: SerializedAppState = {
showTitle: true,
direction: "horizontal",
classes: "",
- blockVariant: "hidden",
+ containerVariant: "hidden",
hidden: false,
flexGrow: 100,
disabled: false
@@ -4869,7 +4869,7 @@ const defaultGraph: SerializedAppState = {
showTitle: true,
direction: "vertical",
classes: "",
- blockVariant: "block",
+ containerVariant: "block",
hidden: false,
flexGrow: 100,
disabled: false
@@ -4908,7 +4908,7 @@ const defaultGraph: SerializedAppState = {
showTitle: true,
direction: "horizontal",
classes: "",
- blockVariant: "block",
+ containerVariant: "block",
hidden: true,
flexGrow: 100,
disabled: false
@@ -4962,7 +4962,7 @@ const defaultGraph: SerializedAppState = {
showTitle: true,
direction: "horizontal",
classes: "",
- blockVariant: "hidden",
+ containerVariant: "hidden",
hidden: false,
flexGrow: 100,
disabled: false
@@ -4984,7 +4984,7 @@ const defaultGraph: SerializedAppState = {
showTitle: true,
direction: "horizontal",
classes: "",
- blockVariant: "hidden",
+ containerVariant: "hidden",
hidden: false,
flexGrow: 100,
disabled: false
@@ -5023,7 +5023,7 @@ const defaultGraph: SerializedAppState = {
showTitle: false,
direction: "horizontal",
classes: "",
- blockVariant: "hidden",
+ containerVariant: "hidden",
hidden: false,
flexGrow: 100,
disabled: false
@@ -5044,7 +5044,7 @@ const defaultGraph: SerializedAppState = {
showTitle: true,
direction: "horizontal",
classes: "",
- blockVariant: "hidden",
+ containerVariant: "hidden",
hidden: false,
flexGrow: 100,
disabled: false
@@ -5116,7 +5116,7 @@ const defaultGraph: SerializedAppState = {
showTitle: true,
direction: "vertical",
classes: "",
- blockVariant: "block",
+ containerVariant: "block",
flexGrow: 100,
disabled: false
}
@@ -5136,7 +5136,7 @@ const defaultGraph: SerializedAppState = {
showTitle: true,
direction: "horizontal",
classes: "",
- blockVariant: "block",
+ containerVariant: "block",
flexGrow: 100,
disabled: false
}
@@ -5241,7 +5241,7 @@ const defaultGraph: SerializedAppState = {
showTitle: true,
direction: "horizontal",
classes: "",
- blockVariant: "hidden",
+ containerVariant: "hidden",
flexGrow: 100,
disabled: false
}
@@ -5261,7 +5261,7 @@ const defaultGraph: SerializedAppState = {
showTitle: true,
direction: "horizontal",
classes: "",
- blockVariant: "hidden",
+ containerVariant: "hidden",
flexGrow: 100,
disabled: false
}
@@ -5331,7 +5331,7 @@ const defaultGraph: SerializedAppState = {
showTitle: true,
direction: "vertical",
classes: "",
- blockVariant: "block",
+ containerVariant: "block",
flexGrow: 100
}
},
@@ -5353,7 +5353,7 @@ const defaultGraph: SerializedAppState = {
showTitle: true,
direction: "horizontal",
classes: "",
- blockVariant: "hidden",
+ containerVariant: "hidden",
flexGrow: 100
}
},
@@ -5372,7 +5372,7 @@ const defaultGraph: SerializedAppState = {
showTitle: true,
direction: "horizontal",
classes: "",
- blockVariant: "hidden",
+ containerVariant: "hidden",
flexGrow: 100
}
},
diff --git a/src/lib/nodes/ComfyWidgetNodes.ts b/src/lib/nodes/ComfyWidgetNodes.ts
index eee5593..782efd7 100644
--- a/src/lib/nodes/ComfyWidgetNodes.ts
+++ b/src/lib/nodes/ComfyWidgetNodes.ts
@@ -489,7 +489,8 @@ LiteGraph.registerNodeType({
})
export interface ComfyButtonProperties extends ComfyWidgetProperties {
- message: string
+ message: string,
+ variant: string
}
export class ComfyButtonNode extends ComfyWidgetNode
{
diff --git a/src/lib/stores/layoutState.ts b/src/lib/stores/layoutState.ts
index 723c419..c0f09b8 100644
--- a/src/lib/stores/layoutState.ts
+++ b/src/lib/stores/layoutState.ts
@@ -6,61 +6,233 @@ import { dndzone, SHADOW_PLACEHOLDER_ITEM_ID } from 'svelte-dnd-action';
import type { ComfyWidgetNode } from '$lib/nodes';
type DragItemEntry = {
+ /*
+ * Drag item.
+ */
dragItem: IDragItem,
+
+ /*
+ * Children of this drag item.
+ * Only applies if the drag item's type is "container"
+ */
children: IDragItem[] | null,
+
+ /*
+ * Parent of this drag item.
+ */
parent: IDragItem | null
}
+/*
+ * Global workflow attributes
+ */
export type LayoutAttributes = {
+ /*
+ * Default subgraph to run when the "Queue Prompt" button in the bottom bar
+ * is pressed.
+ *
+ * If it's an empty string, all backend nodes will be included in the prompt
+ * instead.
+ */
defaultSubgraph: string
}
+/*
+ * Keeps track of the tree of UI components - widgets and the containers that
+ * group them together.
+ */
export type LayoutState = {
+ /*
+ * Root of the UI tree
+ */
root: IDragItem | null,
+
+ /*
+ * All items indexed by their own ID
+ */
allItems: Record,
+
+ /*
+ * Items indexed by the litegraph node they're bound to
+ * Only contains drag items of type "widget"
+ */
allItemsByNode: Record,
+
+ /*
+ * Next ID to use for instantiating a new drag item
+ */
currentId: number,
+
+ /*
+ * Selected drag items.
+ */
currentSelection: DragItemID[],
+
+ /*
+ * Selected LGraphNodes inside the litegraph canvas.
+ */
currentSelectionNodes: LGraphNode[],
+
+ /*
+ * If true, a saved workflow is being deserialized, so ignore any
+ * nodeAdded/nodeRemoved events.
+ *
+ * TODO: instead use LGraphAddNodeOptions.addedByDeserialize
+ */
isConfiguring: boolean,
+
+ /*
+ * If true, the right-click context menu is open
+ */
isMenuOpen: boolean,
+
+ /*
+ * Global workflow attributes
+ */
attrs: LayoutAttributes
}
+/**
+ * Attributes for both containers and nodes, or containers only.
+ * If the attribute can be applicable to both, then it should go here.
+ * If it only applies to a container it should go here too.
+ * If it only applies to a node it should be placed in its LGraphNode.properties (nodeProps) instead.
+ **/
export type Attributes = {
+ /*
+ * Flex direction for containers.
+ */
direction: "horizontal" | "vertical",
+
+ /*
+ * Display name of this item.
+ */
title: string,
+
+ /*
+ * If false, hide the title.
+ */
showTitle: boolean,
+
+ /*
+ * List of classes to apply to the component.
+ */
classes: string,
- blockVariant?: "block" | "hidden",
+
+ /*
+ * Variant for containers. "hidden" hides margin/borders.
+ */
+ containerVariant?: "block" | "hidden",
+
+ /*
+ * If true, don't show this component in the UI
+ */
hidden?: boolean,
+
+ /*
+ * If true, grey out this component in the UI
+ */
disabled?: boolean,
+
+ /*
+ * CSS Flex grow
+ */
flexGrow?: number,
- /** Display variant for widgets/containers (e.g. number widget can act as slider/knob/dial) */
+ /**
+ * Display variant for widgets/containers (e.g. number widget can act as slider/knob/dial)
+ * Valid values depend on the widget in question.
+ */
variant?: string,
+ /*************************************/
+ /* Special attributes for containers */
+ /*************************************/
+
+ // Accordion
openOnStartup?: boolean
}
export type AttributesSpec = {
- id?: number, // for svelte keyed each
+ /*
+ * ID necessary for svelte's keyed each, autoset at the top level in this source file.
+ */
+ id?: number,
+
+ /*
+ * Attribute name. Corresponds to the name of the instance variable in the
+ * hashmap/class instance, which depends on `location`.
+ */
name: string,
- type: string,
+
+ /*
+ * Type of this attribute.
+ * If you want to support a custom type, use "string" combined with
+ * `serialize` and `deserialize`.
+ */
+ type: "string" | "enum" | "number" | "boolean",
+
+ /*
+ * Location of this attribute.
+ * - "widget": inside IDragNode.attrs
+ * - "nodeProps": inside LGraphNode.properties
+ * - "nodeVars": an instance variable directly on an LGraphNode
+ * - "workflow": inside $layoutState.attrs
+ */
location: "widget" | "nodeProps" | "nodeVars" | "workflow"
+
+ /*
+ * Can this attribute be edited in the properties pane.
+ */
editable: boolean,
+
+ /*
+ * Default value to supply to this attribute if it is null when the properties pane is opened.
+ * NOTE: This means that any attribute can't have a default null value!
+ */
defaultValue: any,
+ /*
+ * If `type` is "enum", the valid values for the combo widget.
+ */
values?: string[],
- hidden?: boolean,
+
+ /*
+ * Valid `LGraphNode.type`s this property applies to if it's located in a node.
+ * These are like "ui/button", "ui/slider".
+ */
validNodeTypes?: string[],
+ /*
+ * Callback: if false, don't show the property in the pane.
+ * Useful if you need to show the property based on another property.
+ * Example: If the IDragItem is a container (not a widget), show its flex `direction`.
+ */
canShow?: (arg: IDragItem | LGraphNode) => boolean,
+
+ /*
+ * If the type of this spec is "string", but the underlying type is something else,
+ * convert the value to a string here so it can be edited in the textbox.
+ */
serialize?: (arg: any) => string,
+
+ /*
+ * If the type of this spec is "string", but the underlying type is something else,
+ * convert the textbox value into the underlying value.
+ */
deserialize?: (arg: string) => any,
+
+ /*
+ * If true, when this property is changed the properties pane will be rebuilt.
+ * This should be used if there's a canShow dependent on this property so
+ * the pane can be updated with the new list of valid properties.
+ */
refreshPanelOnChange?: boolean
}
+/*
+ * A list of `AttributesSpec`s grouped under a category.
+ */
export type AttributesCategorySpec = {
categoryName: string,
specs: AttributesSpec[]
@@ -75,6 +247,10 @@ const deserializeStringArray = (arg: string) => {
return arg.split(",").map(s => s.trim())
}
+/*
+ * Attributes that will show up in the properties panel.
+ * Their order in the list is the order they'll appear in the panel.
+ */
const ALL_ATTRIBUTES: AttributesSpecList = [
{
categoryName: "appearance",
@@ -136,7 +312,7 @@ const ALL_ATTRIBUTES: AttributesSpecList = [
refreshPanelOnChange: true
},
{
- name: "blockVariant",
+ name: "containerVariant",
type: "enum",
location: "widget",
editable: true,
@@ -218,6 +394,7 @@ const ALL_ATTRIBUTES: AttributesSpecList = [
}
];
+// This is needed so the specs can be iterated with svelte's keyed #each.
let i = 0;
for (const cat of Object.values(ALL_ATTRIBUTES)) {
for (const val of Object.values(cat.specs)) {
@@ -228,20 +405,54 @@ for (const cat of Object.values(ALL_ATTRIBUTES)) {
export { ALL_ATTRIBUTES };
+/*
+ * Something that can be dragged around in the frontend - a widget or a container.
+ */
export interface IDragItem {
- type: string,
+ /*
+ * Type of the item.
+ */
+ type: "container" | "widget",
+
+ /*
+ * Unique ID of the item.
+ */
id: DragItemID,
+
+ /*
+ * If true, the node associated with this drag item is executing.
+ * Used to show an indicator on the widget/container.
+ */
isNodeExecuting?: boolean,
+
+ /*
+ * Attributes for this drag item.
+ */
attrs: Attributes,
+
+ /*
+ * Hackish thing to indicate to Svelte that an attribute changed.
+ * TODO Use Writeable instead!
+ */
attrsChanged: Writable
}
+/*
+ * A container (block, accordion, tabs). Has child drag items.
+ */
export interface ContainerLayout extends IDragItem {
type: "container",
}
+/*
+ * A widget (slider, dropdown, textbox...)
+ */
export interface WidgetLayout extends IDragItem {
type: "widget",
+
+ /*
+ * litegraph node this widget is bound to.
+ */
node: ComfyWidgetNode
}
@@ -310,7 +521,7 @@ function addContainer(parent: ContainerLayout | null, attrs: Partial
showTitle: true,
direction: "vertical",
classes: "",
- blockVariant: "block",
+ containerVariant: "block",
flexGrow: 100,
...attrs
}
@@ -371,23 +582,10 @@ function nodeAdded(node: LGraphNode) {
const parent = findDefaultContainerForInsertion();
- // Two cases where we want to add nodes:
- // 1. User adds a new UI node, so we should instantiate its widget in the frontend.
- // 2. User adds a node with inputs that can be filled by frontend widgets.
- // Depending on config, this means we should instantiate default UI nodes connected to those inputs.
-
- console.debug(node)
+ console.debug("[layoutState] nodeAdded", node)
if ("svelteComponentType" in node) {
addWidget(parent, node as ComfyWidgetNode);
}
-
- // Add default node panel with all widgets autoinstantiated
- // if (node.widgets && node.widgets.length > 0) {
- // const container = addContainer(parent.id, { title: node.title, direction: "vertical", associatedNode: node.id });
- // for (const widget of node.widgets) {
- // addWidget(container.id, node, widget, { associatedNode: node.id });
- // }
- // }
}
function removeEntry(state: LayoutState, id: DragItemID) {
diff --git a/src/lib/widgets/ButtonWidget.svelte b/src/lib/widgets/ButtonWidget.svelte
index a0c72ed..d1c6c6d 100644
--- a/src/lib/widgets/ButtonWidget.svelte
+++ b/src/lib/widgets/ButtonWidget.svelte
@@ -44,5 +44,10 @@
.wrapper {
padding: 2px;
width: 100%;
+ height: 100%;
+
+ :global(> button) {
+ height: 100%;
+ }
}
diff --git a/src/lib/widgets/TextWidget.svelte b/src/lib/widgets/TextWidget.svelte
index 61704a4..67d8565 100644
--- a/src/lib/widgets/TextWidget.svelte
+++ b/src/lib/widgets/TextWidget.svelte
@@ -35,7 +35,7 @@
disabled={widget.attrs.disabled}
lines={node.properties.multiline ? 5 : 1}
max_lines={node.properties.multiline ? 5 : 1}
- show_label={true}
+ show_label={widget.attrs.title !== ""}
on:change
on:submit
on:blur
@@ -49,4 +49,8 @@
padding: 2px;
width: 100%;
}
+
+ :global(span.hide) {
+ display: none;
+ }
diff --git a/vite.config.ts b/vite.config.ts
index ac36164..5288058 100644
--- a/vite.config.ts
+++ b/vite.config.ts
@@ -8,12 +8,12 @@ import { viteStaticCopy } from 'vite-plugin-static-copy'
export default defineConfig({
clearScreen: false,
plugins: [
- FullReload([
- // "src/**/*.{js,ts,scss,svelte}"
- "src/**/*.{scss}",
- "src/lib/stores/*.*",
- "src/**/ComfyApp.{ts,svelte}"
- ]),
+ // FullReload([
+ // // "src/**/*.{js,ts,scss,svelte}"
+ // "src/**/*.{scss}",
+ // "src/lib/stores/*.*",
+ // "src/**/ComfyApp.{ts,svelte}"
+ // ]),
svelte(),
viteStaticCopy({
targets: [
From e76204936ba7abacfe986c7214f15c9ddc5e80e6 Mon Sep 17 00:00:00 2001
From: space-nuko <24979496+space-nuko@users.noreply.github.com>
Date: Sat, 6 May 2023 14:15:57 -0500
Subject: [PATCH 07/19] Tab styles
---
src/lib/components/TabsContainer.svelte | 2 +-
src/scss/ux.scss | 21 +++++++++++++++++++++
2 files changed, 22 insertions(+), 1 deletion(-)
diff --git a/src/lib/components/TabsContainer.svelte b/src/lib/components/TabsContainer.svelte
index 067fa7b..8a17bcd 100644
--- a/src/lib/components/TabsContainer.svelte
+++ b/src/lib/components/TabsContainer.svelte
@@ -111,7 +111,7 @@
{/if}
{:else}
-
+
{#each children.filter(item => item.id !== SHADOW_PLACEHOLDER_ITEM_ID) as item, i(item.id)}
{@const tabName = getTabName(container, i)}
console.log("tab " + i)}>
diff --git a/src/scss/ux.scss b/src/scss/ux.scss
index b5943b4..d460f50 100644
--- a/src/scss/ux.scss
+++ b/src/scss/ux.scss
@@ -332,6 +332,27 @@ div.float {
opacity: 0 !important;
}
+.gradio-tabs.tabs {
+ > .tab-nav {
+ border-bottom: 1px solid var(--ae-subpanel-border-color);
+ > button {
+ border-radius: 0;
+ border-width: var(--ae-border-width);
+ color: var(--ae-text-color);
+
+ &.selected {
+ border-color: var(--ae-subpanel-border-color);
+ background: var(--ae-subpanel-bg-color);
+ color: var(--ae-primary-color);
+ }
+ }
+ }
+ > .tabitem {
+ border: 1px solid var(--ae-subpanel-border-color);
+ border-top: none;
+ border-radius: 0px !important;
+ }
+}
.form>.gradio-row>.form{
border:0 !important;
From 81618da30340fe416a4161178fdbc3a9b3cc0f7f Mon Sep 17 00:00:00 2001
From: space-nuko <24979496+space-nuko@users.noreply.github.com>
Date: Sat, 6 May 2023 15:10:37 -0500
Subject: [PATCH 08/19] Graph component
---
src/lib/components/ComfyApp.svelte | 45 ++++-------
src/lib/components/ComfyApp.ts | 2 +
src/lib/components/ComfyGraphView.svelte | 76 +++++++++++++++++++
src/lib/components/ComfyProperties.svelte | 32 ++++++--
src/lib/components/ComfyUnlockUIButton.svelte | 4 +-
src/scss/ux.scss | 20 +++++
6 files changed, 141 insertions(+), 38 deletions(-)
create mode 100644 src/lib/components/ComfyGraphView.svelte
diff --git a/src/lib/components/ComfyApp.svelte b/src/lib/components/ComfyApp.svelte
index ac2f83c..e9b3c88 100644
--- a/src/lib/components/ComfyApp.svelte
+++ b/src/lib/components/ComfyApp.svelte
@@ -20,6 +20,7 @@
import ComfyProperties from "./ComfyProperties.svelte";
import queueState from "$lib/stores/queueState";
import ComfyUnlockUIButton from "./ComfyUnlockUIButton.svelte";
+ import ComfyGraphView from "./ComfyGraphView.svelte";
export let app: ComfyApp = undefined;
let imageViewer: ImageViewer;
@@ -59,6 +60,7 @@
$layoutState.currentSelection = []
let graphSize = 0;
+ let graphTransitioning = false;
function toggleGraph() {
if (graphSize == 0) {
@@ -121,10 +123,6 @@
}
}
- function doRecenter(): void {
- app.lCanvas.recenter();
- }
-
$: if ($uiState.uiUnlocked && !hasShownUIHelpToast) {
hasShownUIHelpToast = true;
toast.push("Right-click to open context menu.")
@@ -145,11 +143,18 @@
}
$: if (containerElem) {
- let wrappers = containerElem.querySelectorAll(".pane-wrapper")
- for (const wrapper of wrappers) {
- const paneNode = wrapper.parentNode as HTMLElement; // get the node inside the
- paneNode.ontransitionend = () => {
- app.resizeCanvas()
+ const canvas = containerElem.querySelector("#graph-canvas")
+ if (canvas) {
+ console.warn("reg!");
+ const paneNode = canvas.closest(".splitpanes__pane")
+ if (paneNode) {
+ (paneNode as HTMLElement).ontransitionstart = () => {
+ graphTransitioning = true
+ }
+ (paneNode as HTMLElement).ontransitionend = () => {
+ graphTransitioning = false
+ app.resizeCanvas()
+ }
}
}
}
@@ -190,9 +195,7 @@
-
-
-
+
@@ -226,9 +229,6 @@
-
@@ -273,15 +273,6 @@
height: 100vh;
}
- #comfy-ui {
- }
-
- #comfy-graph {
- }
-
- #graph-canvas {
- }
-
#bottombar {
padding-top: 0.5em;
display: flex;
@@ -302,12 +293,6 @@
}
}
- .canvas-wrapper {
- width: 100%;
- height: 100%;
- background-color: #333;
- }
-
.sidebar-wrapper {
width: 100%;
height: 100%;
diff --git a/src/lib/components/ComfyApp.ts b/src/lib/components/ComfyApp.ts
index 794064f..2070e00 100644
--- a/src/lib/components/ComfyApp.ts
+++ b/src/lib/components/ComfyApp.ts
@@ -106,6 +106,8 @@ export default class ComfyApp {
LiteGraph.release_link_on_empty_shows_menu = true;
LiteGraph.alt_drag_do_clone_nodes = true;
+ (window as any).LiteGraph = LiteGraph;
+
// await this.#invokeExtensionsAsync("init");
await this.registerNodes();
diff --git a/src/lib/components/ComfyGraphView.svelte b/src/lib/components/ComfyGraphView.svelte
new file mode 100644
index 0000000..f272fcc
--- /dev/null
+++ b/src/lib/components/ComfyGraphView.svelte
@@ -0,0 +1,76 @@
+
+
+
+
+
+
+
+ {#if !transitioning}
+
+
+
+ {/if}
+
+
+
+
diff --git a/src/lib/components/ComfyProperties.svelte b/src/lib/components/ComfyProperties.svelte
index 5e9e871..b4fe006 100644
--- a/src/lib/components/ComfyProperties.svelte
+++ b/src/lib/components/ComfyProperties.svelte
@@ -65,7 +65,7 @@
}
function validNodeProperty(spec: AttributesSpec, node: LGraphNode | null): boolean {
- if (node == null)
+ if (node == null || spec.location !== "nodeProps")
return false;
if (spec.canShow && !spec.canShow(node))
@@ -77,8 +77,21 @@
return spec.name in node.properties
}
+ function validNodeVar(spec: AttributesSpec, node: LGraphNode | null): boolean {
+ if (node == null || spec.location !== "nodeVars")
+ return false;
+
+ if (spec.canShow && !spec.canShow(node))
+ return false;
+
+ if (spec.validNodeTypes) {
+ return spec.validNodeTypes.indexOf(node.type) !== -1;
+ }
+ return spec.name in node
+ }
+
function validWidgetAttribute(spec: AttributesSpec, widget: IDragItem | null): boolean {
- if (widget == null)
+ if (widget == null || spec.location !== "widget")
return false;
if (spec.canShow)
return spec.canShow(widget);
@@ -86,6 +99,13 @@
return spec.name in widget.attrs
}
+ function validWorkflowAttribute(spec: AttributesSpec): boolean {
+ if (spec.location !== "workflow")
+ return false;
+
+ return spec.name in $layoutState.attrs
+ }
+
function getAttribute(target: IDragItem, spec: AttributesSpec): any {
let value = target.attrs[spec.name]
if (value == null)
@@ -200,7 +220,7 @@
{#each category.specs as spec(spec.id)}
- {#if spec.location === "widget" && validWidgetAttribute(spec, target)}
+ {#if validWidgetAttribute(spec, target)}