From 2a297a5516adb16ba0e117eceebbce5ceea55844 Mon Sep 17 00:00:00 2001 From: Leon Date: Tue, 2 Sep 2025 00:00:32 +0200 Subject: [PATCH 1/4] Render Stimulus identifiers found on the page --- src/browser_panel/State.svelte.js | 9 ++ src/browser_panel/messaging.js | 6 +- src/browser_panel/page/backend.js | 37 ++++- src/browser_panel/page/stimulus_observer.js | 136 ++++++++++++++++++ src/browser_panel/panel/App.svelte | 3 +- src/browser_panel/panel/tabs/LogsTab.svelte | 4 - .../panel/tabs/StimulusTab.svelte | 75 ++++++++++ src/lib/constants.js | 1 + 8 files changed, 263 insertions(+), 8 deletions(-) create mode 100644 src/browser_panel/page/stimulus_observer.js create mode 100644 src/browser_panel/panel/tabs/StimulusTab.svelte diff --git a/src/browser_panel/State.svelte.js b/src/browser_panel/State.svelte.js index 1cbf161..8415334 100644 --- a/src/browser_panel/State.svelte.js +++ b/src/browser_panel/State.svelte.js @@ -24,6 +24,7 @@ let turboFrames = $state([]) let turboCables = $state([]) let turboStreams = $state([]) let turboEvents = $state([]) +let stimulusData = $state([]) export function setTurboFrames(frames, url) { turboFrames = frames @@ -41,6 +42,14 @@ export function getTurboCables() { return turboCables } +export function setStimulusData(data, url) { + stimulusData = data +} + +export function getStimulusData() { + return stimulusData +} + export function addTurboEvent(event) { const exists = turboEvents.some((e) => e.uuid === event.uuid) if (exists) return diff --git a/src/browser_panel/messaging.js b/src/browser_panel/messaging.js index 8ff90b4..e6f6c64 100644 --- a/src/browser_panel/messaging.js +++ b/src/browser_panel/messaging.js @@ -1,5 +1,5 @@ import { PANEL_TO_BACKEND_MESSAGES, BACKEND_TO_PANEL_MESSAGES, PORT_IDENTIFIERS, HOTWIRE_DEV_TOOLS_PANEL_SOURCE } from "$lib/constants" -import { setTurboFrames, setTurboCables, addTurboStream, addTurboEvent } from "./State.svelte.js" +import { setTurboFrames, setTurboCables, setStimulusData, addTurboStream, addTurboEvent } from "./State.svelte.js" function setPort(port) { if (!window.__HotwireDevTools) { @@ -22,6 +22,10 @@ export const handleBackendToPanelMessage = (message, port) => { setTurboCables(message.turboCables, message.url) setPort(port) break + case BACKEND_TO_PANEL_MESSAGES.SET_STIMULUS_CONTROLLERS: + setStimulusData(message.stimulusData, message.url) + setPort(port) + break case BACKEND_TO_PANEL_MESSAGES.TURBO_STREAM_RECEIVED: addTurboStream(message.turboStream) setPort(port) diff --git a/src/browser_panel/page/backend.js b/src/browser_panel/page/backend.js index 9227d65..6f5daa8 100644 --- a/src/browser_panel/page/backend.js +++ b/src/browser_panel/page/backend.js @@ -3,6 +3,7 @@ import { addHighlightOverlayToElements, removeHighlightOverlay } from "$utils/hi import { debounce, generateUUID, getElementPath, getElementFromIndexPath, stringifyHTMLElementTag, stringifyHTMLElementTagShallow, safeStringifyEventDetail } from "$utils/utils" import TurboFrameObserver from "./turbo_frame_observer.js" import TurboCableObserver from "./turbo_cable_observer.js" +import StimulusObserver from "./stimulus_observer.js" import ElementObserver from "./element_observer.js" // This is the backend script which interacts with the page's DOM. @@ -14,6 +15,7 @@ function init() { this.observers = { turboFrame: new TurboFrameObserver(this), turboCable: new TurboCableObserver(this), + stimulus: new StimulusObserver(this), } this.elementObserver = new ElementObserver(document, this) @@ -44,11 +46,11 @@ function init() { // ElementObserver delegate methods matchElement(element) { - return this.observers.turboFrame.matchElement(element) || this.observers.turboCable.matchElement(element) + return this.observers.turboFrame.matchElement(element) || this.observers.turboCable.matchElement(element) || this.observers.stimulus.matchElement(element) } matchElementsInTree(tree) { - return [...this.observers.turboFrame.matchElementsInTree(tree), ...this.observers.turboCable.matchElementsInTree(tree)] + return [...this.observers.turboFrame.matchElementsInTree(tree), ...this.observers.turboCable.matchElementsInTree(tree), ...this.observers.stimulus.matchElementsInTree(tree)] } elementMatched(element) { @@ -59,6 +61,10 @@ function init() { if (this.observers.turboCable.matchElement(element)) { this.observers.turboCable.elementMatched(element) } + + if (this.observers.stimulus.matchElement(element)) { + this.observers.stimulus.elementMatched(element) + } } elementUnmatched(element) { @@ -69,6 +75,10 @@ function init() { if (this.observers.turboCable.matchElement(element)) { this.observers.turboCable.elementUnmatched(element) } + + if (this.observers.stimulus.matchElement(element)) { + this.observers.stimulus.elementUnmatched(element) + } } elementAttributeChanged(element, attributeName, oldValue) { @@ -79,6 +89,10 @@ function init() { if (this.observers.turboCable.matchElement(element)) { this.observers.turboCable.elementAttributeChanged(element, attributeName, oldValue) } + + if (this.observers.stimulus.matchElement(element)) { + this.observers.stimulus.elementAttributeChanged(element, attributeName, oldValue) + } } // TurboFrameObserver delegate methods @@ -103,6 +117,17 @@ function init() { this.sendTurboCableData() } + // Stimulus delegate methods + stimulusControllerConnected(element) { + this.sendStimulusData() + } + stimulusControllerDisonnected(element) { + this.sendStimulusData() + } + stimulusControllerChanged(element, attributeName, oldValue, newValue) { + this.sendStimulusData() + } + sendTurboFrames = debounce(() => { this._postMessage({ frames: this.observers.turboFrame.getFrameData(), @@ -161,6 +186,14 @@ function init() { }) } + sendStimulusData = debounce(() => { + this._postMessage({ + stimulusData: this.observers.stimulus.getStimulusData(), + url: btoa(window.location.href), + type: BACKEND_TO_PANEL_MESSAGES.SET_STIMULUS_CONTROLLERS, + }) + }, 10) + handleIncomingTurboStream = (event) => { const turboStream = event.target const turboStreamContent = turboStream.outerHTML diff --git a/src/browser_panel/page/stimulus_observer.js b/src/browser_panel/page/stimulus_observer.js new file mode 100644 index 0000000..5972837 --- /dev/null +++ b/src/browser_panel/page/stimulus_observer.js @@ -0,0 +1,136 @@ +// ;[ +// { +// controllers: [ +// { +// identifier: "my-controller", +// selector: "[data-controller~='my-controller']", +// values: [{ name: "auto-start", value: "true" }], +// class: "my-controller", +// }, +// ], +// targets: [{ name: "item", selector: "[data-my-controller-target='item']" }], +// }, +// ] + +import { ensureUUIDOnElement, getUUIDFromElement, stringifyHTMLElementTag, getElementPath } from "$utils/utils.js" + +export default class StimulusObserver { + constructor(delegate) { + console.log("StimulusObserver initialized") + + this.delegate = delegate + this.controllers = new Map() // UUID -> controller data + } + + matchElement(element) { + if (element.dataset?.controller !== undefined) return true + + return false + // return this.elementHasStimulusAttributes(element) + } + + matchElementsInTree(tree) { + const match = this.matchElement(tree) ? [tree] : [] + const matches = Array.from(tree.querySelectorAll("*")).filter((el) => this.matchElement(el)) + return match.concat(matches) + } + + elementMatched(element) { + const uuid = ensureUUIDOnElement(element) + + if (!this.controllers.has(uuid)) { + const controllerData = this.buildStimulusElementData(element) + this.controllers.set(uuid, controllerData) + this.delegate.stimulusControllerConnected(element) + } + } + + elementUnmatched(element) { + const uuid = getUUIDFromElement(element) + + if (this.controllers.has(uuid)) { + this.controllers.delete(uuid) + this.delegate.stimulusControllerDisonnected(element) + } + } + + elementAttributeChanged(element, attributeName, oldValue) { + if (this.matchElement(element)) { + const uuid = getUUIDFromElement(element) + if (this.controllers.has(uuid)) { + const controllerData = this.controllers.get(uuid) + const newValue = element.getAttribute(attributeName) + + if (newValue === null) { + delete controllerData.attributes[attributeName] + } else { + controllerData.attributes[attributeName] = newValue + } + + controllerData.serializedTag = stringifyHTMLElementTag(element) + + this.delegate.stimulusControllerChanged(element, attributeName, oldValue, newValue) + } + } + } + + buildStimulusElementData(element) { + return { + id: element.id, + uuid: getUUIDFromElement(element), + identifier: element.dataset.controller, + serializedTag: stringifyHTMLElementTag(element), + attributes: Array.from(element.attributes).reduce((map, attr) => { + map[attr.name] = attr.value + return map + }, {}), + children: [], + element, + } + } + + getStimulusData() { + const buildStimulusTree = () => { + const root = [] + this.controllers.forEach((controllerData) => { + controllerData.children = [] + }) + + this.controllers.forEach((controllerData) => { + const element = controllerData.element + const parentElement = element.parentElement?.closest("[data-controller]") + + if (parentElement) { + const parentUUID = getUUIDFromElement(parentElement) + if (parentUUID && this.controllers.has(parentUUID)) { + this.controllers.get(parentUUID).children.push(controllerData) + } else { + // Parent exists but not in our tracking => add as root + root.push(controllerData) + } + } else { + // No parent frame => this is a root frame + root.push(controllerData) + } + }) + + return root + } + + // Remove DOM elements before sending + const stripDOMElements = (frameData) => { + const { element, children, ...cleanData } = frameData + const strippedChildren = children.map((child) => stripDOMElements(child)) + return { ...cleanData, children: strippedChildren } + } + + const controllerTree = buildStimulusTree() + return controllerTree.map((element) => stripDOMElements(element)) + } + + elementHasStimulusAttributes(element) { + return Array.from(element.attributes).some((attr) => { + return attr.name.startsWith("data-") && (attr.name.endsWith("-target") || attr.name.endsWith("-value") || attr.name.endsWith("-action") || attr.name.endsWith("-outlet") || attr.name.endsWith("-class")) + }) + } +} diff --git a/src/browser_panel/panel/App.svelte b/src/browser_panel/panel/App.svelte index a03af62..66e41b4 100644 --- a/src/browser_panel/panel/App.svelte +++ b/src/browser_panel/panel/App.svelte @@ -18,6 +18,7 @@ import { getDevtoolInstance, setDevtoolInstance } from "$lib/devtool.js" import { handleResize } from "../theme.svelte.js" import { connection } from "../State.svelte.js" + import StimulusTab from "./tabs/StimulusTab.svelte" import TurboTab from "./tabs/TurboTab.svelte" import LogsTab from "./tabs/LogsTab.svelte" @@ -79,7 +80,7 @@
-

Stimulus

+
diff --git a/src/browser_panel/panel/tabs/LogsTab.svelte b/src/browser_panel/panel/tabs/LogsTab.svelte index 850da65..4eebbc6 100644 --- a/src/browser_panel/panel/tabs/LogsTab.svelte +++ b/src/browser_panel/panel/tabs/LogsTab.svelte @@ -300,8 +300,4 @@ .turbo-events-table { table-layout: fixed; } - - .turbo-cable-icon { - padding: var(--sl-spacing-3x-small); - } diff --git a/src/browser_panel/panel/tabs/StimulusTab.svelte b/src/browser_panel/panel/tabs/StimulusTab.svelte new file mode 100644 index 0000000..b852394 --- /dev/null +++ b/src/browser_panel/panel/tabs/StimulusTab.svelte @@ -0,0 +1,75 @@ + + + + +
+
+
+

Controllers

+
+ {#if stimulusControllers.length > 0} + {#each uniqueStimulusIdentifiers as identifier, index (identifier)} +
+
+
{identifier}
+
+
+ {/each} + {:else} +
+ No Stimulus Controllers seen yet + We'll keep looking +
+ {/if} +
+
+
+
+
+ +
diff --git a/src/lib/constants.js b/src/lib/constants.js index 6328b08..c1261ba 100644 --- a/src/lib/constants.js +++ b/src/lib/constants.js @@ -10,6 +10,7 @@ export const PORT_IDENTIFIERS = { export const BACKEND_TO_PANEL_MESSAGES = { SET_TURBO_FRAMES: "set-turbo-frames", SET_TURBO_CABLES: "set-turbo-cables", + SET_STIMULUS_CONTROLLERS: "set-stimulus-controllers", TURBO_STREAM_RECEIVED: "turbo-stream-received", TURBO_EVENT_RECEIVED: "turbo-event-received", HEALTH_CHECK_RESPONSE: "health-check-response", From 572aad018f9af4e9473b582a7c4d95fda907e458 Mon Sep 17 00:00:00 2001 From: Leon Date: Mon, 8 Sep 2025 21:44:58 +0200 Subject: [PATCH 2/4] List Stimulus controller instances --- src/browser_panel/page/stimulus_observer.js | 66 ++++++----- .../panel/tabs/StimulusTab.svelte | 104 ++++++++++++++---- src/utils/utils.js | 4 + 3 files changed, 125 insertions(+), 49 deletions(-) diff --git a/src/browser_panel/page/stimulus_observer.js b/src/browser_panel/page/stimulus_observer.js index 5972837..739de6f 100644 --- a/src/browser_panel/page/stimulus_observer.js +++ b/src/browser_panel/page/stimulus_observer.js @@ -16,10 +16,8 @@ import { ensureUUIDOnElement, getUUIDFromElement, stringifyHTMLElementTag, getEl export default class StimulusObserver { constructor(delegate) { - console.log("StimulusObserver initialized") - this.delegate = delegate - this.controllers = new Map() // UUID -> controller data + this.controllerElements = new Map() // UUID -> [controller data] } matchElement(element) { @@ -36,20 +34,27 @@ export default class StimulusObserver { } elementMatched(element) { - const uuid = ensureUUIDOnElement(element) + const identifiers = (element.dataset.controller || "") + .split(" ") + .map((id) => id.trim()) + .filter((id) => id.length > 0) - if (!this.controllers.has(uuid)) { - const controllerData = this.buildStimulusElementData(element) - this.controllers.set(uuid, controllerData) - this.delegate.stimulusControllerConnected(element) + const uuid = ensureUUIDOnElement(element) + if (!this.controllerElements.has(uuid)) { + this.controllerElements.set(uuid, []) } + identifiers.forEach((identifier) => { + const controllerData = this.buildStimulusElementData(element, identifier) + this.controllerElements.get(uuid).push(controllerData) + }) + this.delegate.stimulusControllerConnected(element) } elementUnmatched(element) { const uuid = getUUIDFromElement(element) - if (this.controllers.has(uuid)) { - this.controllers.delete(uuid) + if (this.controllerElements.has(uuid)) { + this.controllerElements.delete(uuid) this.delegate.stimulusControllerDisonnected(element) } } @@ -57,28 +62,28 @@ export default class StimulusObserver { elementAttributeChanged(element, attributeName, oldValue) { if (this.matchElement(element)) { const uuid = getUUIDFromElement(element) - if (this.controllers.has(uuid)) { - const controllerData = this.controllers.get(uuid) + if (this.controllerElements.has(uuid)) { const newValue = element.getAttribute(attributeName) + this.controllerElements.get(uuid).forEach((controllerData) => { + if (newValue === null) { + delete controllerData.attributes[attributeName] + } else { + controllerData.attributes[attributeName] = newValue + } - if (newValue === null) { - delete controllerData.attributes[attributeName] - } else { - controllerData.attributes[attributeName] = newValue - } - - controllerData.serializedTag = stringifyHTMLElementTag(element) + controllerData.serializedTag = stringifyHTMLElementTag(element) + }) this.delegate.stimulusControllerChanged(element, attributeName, oldValue, newValue) } } } - buildStimulusElementData(element) { + buildStimulusElementData(element, identifier) { return { id: element.id, uuid: getUUIDFromElement(element), - identifier: element.dataset.controller, + identifier: identifier, serializedTag: stringifyHTMLElementTag(element), attributes: Array.from(element.attributes).reduce((map, attr) => { map[attr.name] = attr.value @@ -92,18 +97,23 @@ export default class StimulusObserver { getStimulusData() { const buildStimulusTree = () => { const root = [] - this.controllers.forEach((controllerData) => { - controllerData.children = [] + this.controllerElements.forEach((controllersData) => { + controllersData.forEach((controllerData) => { + controllerData.children = [] + }) }) - this.controllers.forEach((controllerData) => { + this.controllerElements.forEach((controllersData) => { + const controllerData = controllersData[0] const element = controllerData.element const parentElement = element.parentElement?.closest("[data-controller]") if (parentElement) { const parentUUID = getUUIDFromElement(parentElement) - if (parentUUID && this.controllers.has(parentUUID)) { - this.controllers.get(parentUUID).children.push(controllerData) + if (parentUUID && this.controllerElements.has(parentUUID)) { + this.controllerElements.get(parentUUID).forEach((parentControllerData) => { + parentControllerData.children.push(controllerData) + }) } else { // Parent exists but not in our tracking => add as root root.push(controllerData) @@ -118,8 +128,8 @@ export default class StimulusObserver { } // Remove DOM elements before sending - const stripDOMElements = (frameData) => { - const { element, children, ...cleanData } = frameData + const stripDOMElements = (data) => { + const { element, children, ...cleanData } = data const strippedChildren = children.map((child) => stripDOMElements(child)) return { ...cleanData, children: strippedChildren } } diff --git a/src/browser_panel/panel/tabs/StimulusTab.svelte b/src/browser_panel/panel/tabs/StimulusTab.svelte index b852394..9550c3b 100644 --- a/src/browser_panel/panel/tabs/StimulusTab.svelte +++ b/src/browser_panel/panel/tabs/StimulusTab.svelte @@ -8,7 +8,7 @@ import IconButton from "$shoelace/IconButton.svelte" import HTMLRenderer from "$src/browser_panel/HTMLRenderer.svelte" import { getStimulusData } from "../../State.svelte.js" - import { debounce, handleKeyboardNavigation } from "$utils/utils.js" + import { flattenNodes } from "$utils/utils.js" import { panelPostMessage, addHighlightOverlay, addHighlightOverlayByPath, hideHighlightOverlay } from "../../messaging.js" import { HOTWIRE_DEV_TOOLS_PANEL_SOURCE, PANEL_TO_BACKEND_MESSAGES } from "$lib/constants.js" import { getDevtoolInstance } from "$lib/devtool.js" @@ -16,33 +16,37 @@ import * as Icons from "$utils/icons.js" import { collapseEntryRows, toggleStickyParent, checkStickyVisibility } from "$src/utils/collapsible.js" - const SELECTABLE_TYPES = { - TURBO_FRAME: "turbo-frame", - TURBO_STREAM: "turbo-stream", - } - const turboStreamAnimationDuration = 300 - const ignoredAttributes = ["id", "data-hotwire-dev-tools-uuid", "style"] - const devTool = getDevtoolInstance() let options = $state(devTool.options) + let selected = $state({ + identifier: null, + uuid: null, + }) let stimulusControllers = $state([]) - let uniqueStimulusIdentifiers = $derived(flattenIdentifiers(stimulusControllers)) + let flattenStimulusControllers = $derived(flattenNodes(stimulusControllers)) + let uniqueIdentifiers = $derived([...new Set(flattenStimulusControllers.map((n) => n.identifier).filter(Boolean))].sort()) + let counts = $derived( + flattenStimulusControllers.reduce((acc, n) => { + if (n.identifier) acc[n.identifier] = (acc[n.identifier] || 0) + 1 + return acc + }, {}), + ) $effect(() => { stimulusControllers = getStimulusData() }) - const flattenIdentifiers = (tree) => { - const ids = new Set() - const traverse = (nodes) => { - for (const { identifier, children } of nodes) { - ids.add(identifier) - if (children) traverse(children) - } + const getStimulusInstances = (identifier) => { + return flattenStimulusControllers.filter((n) => n.identifier === identifier) + } + + const setSelectedIdentifier = (identifier) => { + selected = { + identifier: identifier, } - traverse(tree) - return [...ids].sort() } + + const handleEventListKeyboardNavigation = (event) => {} @@ -53,10 +57,18 @@

Controllers

{#if stimulusControllers.length > 0} - {#each uniqueStimulusIdentifiers as identifier, index (identifier)} + {#each uniqueIdentifiers as identifier, index (identifier)}
-
+
setSelectedIdentifier(identifier)} + onkeyup={handleEventListKeyboardNavigation} + >
{identifier}
+
{counts[identifier]}
{/each} @@ -71,5 +83,55 @@
- + +
+
+ {#if selected.identifier} +
+

Details for {selected.identifier}

+
+ {#each getStimulusInstances(selected.identifier) as instance (instance.uuid)} +
+
+
+ {instance.identifier} +
+ +
+
+
+
+
+ {/each} + {:else} +
+ Nothing selected + Select a Turbo Event to see its details +
+ {/if} +
+
+
+ + diff --git a/src/utils/utils.js b/src/utils/utils.js index e3ae028..269dd52 100644 --- a/src/utils/utils.js +++ b/src/utils/utils.js @@ -166,3 +166,7 @@ export const safeStringifyEventDetail = (detail) => { }), ) } + +export const flattenNodes = (tree) => { + return tree.flatMap((node) => [node, ...flattenNodes(node.children || [])]) +} From 33488688a4d090c22f16c45d2e8e74730c566910 Mon Sep 17 00:00:00 2001 From: Leon Date: Mon, 8 Sep 2025 22:06:01 +0200 Subject: [PATCH 3/4] Add keyboard navigation --- public/styles/dev_tool_panel.css | 4 ++ src/browser_panel/panel/tabs/LogsTab.svelte | 5 +- .../panel/tabs/StimulusTab.svelte | 55 +++++++++++-------- 3 files changed, 36 insertions(+), 28 deletions(-) diff --git a/public/styles/dev_tool_panel.css b/public/styles/dev_tool_panel.css index c2ac08c..a04e1e3 100644 --- a/public/styles/dev_tool_panel.css +++ b/public/styles/dev_tool_panel.css @@ -640,6 +640,10 @@ svg.icon-muted { } } +.d-table-row { + display: table-row; +} + /* Highlight.js GitHub Theme */ pre code.hljs { display: block; diff --git a/src/browser_panel/panel/tabs/LogsTab.svelte b/src/browser_panel/panel/tabs/LogsTab.svelte index 4eebbc6..e8f7981 100644 --- a/src/browser_panel/panel/tabs/LogsTab.svelte +++ b/src/browser_panel/panel/tabs/LogsTab.svelte @@ -161,7 +161,7 @@ onmouseenter={() => addHighlightOverlayByPath(event.targetElementPath)} onmouseleave={() => hideHighlightOverlay()} > -
+
{event.eventName}
@@ -281,9 +281,6 @@ From ff8573b43ea8dcfae972b07c15f54dbe5f00e7a6 Mon Sep 17 00:00:00 2001 From: Leon Date: Tue, 9 Sep 2025 10:21:11 +0200 Subject: [PATCH 4/4] List Stimulus Controller instance values --- src/browser_panel/page/stimulus_observer.js | 20 ++++++++ .../panel/tabs/StimulusTab.svelte | 51 ++++++++++++++++++- 2 files changed, 70 insertions(+), 1 deletion(-) diff --git a/src/browser_panel/page/stimulus_observer.js b/src/browser_panel/page/stimulus_observer.js index 739de6f..2da53c3 100644 --- a/src/browser_panel/page/stimulus_observer.js +++ b/src/browser_panel/page/stimulus_observer.js @@ -80,6 +80,7 @@ export default class StimulusObserver { } buildStimulusElementData(element, identifier) { + const controller = window.Stimulus.getControllerForElementAndIdentifier(element, identifier) return { id: element.id, uuid: getUUIDFromElement(element), @@ -89,11 +90,30 @@ export default class StimulusObserver { map[attr.name] = attr.value return map }, {}), + values: this.buildControllerValues(controller), + targets: this.buildControllerTargets(controller), children: [], element, } } + buildControllerValues(controller) { + return Object.values(controller.valueDescriptorMap).map((descriptor) => { + return { + key: descriptor.key, + name: descriptor.name, + type: descriptor.type, + defaultValue: descriptor.defaultValue, + value: controller[descriptor.name], + } + }) + } + + buildControllerTargets(controller) { + const keys = Object.keys(Object.getOwnPropertyDescriptors(Object.getPrototypeOf(controller))) + return keys.filter((key) => key.endsWith("Target") && !key.startsWith("has")) + } + getStimulusData() { const buildStimulusTree = () => { const root = [] diff --git a/src/browser_panel/panel/tabs/StimulusTab.svelte b/src/browser_panel/panel/tabs/StimulusTab.svelte index a0416e2..52b7b5c 100644 --- a/src/browser_panel/panel/tabs/StimulusTab.svelte +++ b/src/browser_panel/panel/tabs/StimulusTab.svelte @@ -21,6 +21,7 @@ let selected = $state({ identifier: null, uuid: null, + controller: null, }) let stimulusControllers = $state([]) let flattenStimulusControllers = $derived(flattenNodes(stimulusControllers)) @@ -46,6 +47,14 @@ } } + const setSelectedController = (instance) => { + selected = { + uuid: instance.uuid, + controller: instance, + identifier: instance.identifier, + } + } + const handleStimulusIdentifiersKeyboardNavigation = (event) => { if (!uniqueIdentifiers.length) return @@ -97,6 +106,7 @@
+
@@ -105,7 +115,14 @@

Details for {selected.identifier}

{#each getStimulusInstances(selected.identifier) as instance (instance.uuid)} -
+
setSelectedController(instance)} + onkeyup={handleStimulusIdentifiersKeyboardNavigation} + >
{instance.identifier} @@ -126,6 +143,38 @@
+ + +
+
+ {#if selected.controller} +
+
+
Values
+ {console.log("selected.controller.values", Object.entries(selected.controller.values))} + + + {#each Object.entries(selected.controller.values) as [_key, object]} + {#each Object.entries(object) as [key, value]} + + + + + {/each} + {/each} + +
{key}
{value}
+
+
+ {:else} +
+ Nothing selected + Select a Stimulus Controller to see its details +
+ {/if} +
+