[0],
- );
- }
-
- /*
- * Deliberately not deduplicated here. Tracking what was last applied meant
- * the runtime held state that drifted out of step with the component, which
- * silently swallowed real changes. The component compares against its own
- * selection instead, which cannot drift.
- */
- expect(seen).toEqual(["two", "two", "two"]);
-});
diff --git a/packages/ui/components/Tabs.wrn b/packages/ui/components/Tabs.wrn
index 112b65a0..ac309768 100644
--- a/packages/ui/components/Tabs.wrn
+++ b/packages/ui/components/Tabs.wrn
@@ -38,15 +38,23 @@ component Tabs {
return String(item.value || item.id || index)
}
+ /*
+ * In url mode the query parameter is the source of truth, not local state.
+ *
+ * The client router owns popstate and swaps the whole page on back and
+ * forward, which discards component state anyway. Reading the URL means
+ * the right tab simply falls out of whatever render happens next, with no
+ * listener to lose and nothing to keep in step.
+ */
shared function currentValue() {
+ if (mode === "url" && typeof window !== "undefined" && window.location) {
+ var fromUrl = new URLSearchParams(window.location.search).get(param || "tab")
+ return fromUrl ? fromUrl : defaultValue()
+ }
if (activeValue) {
return activeValue
}
- if (active) {
- return active
- }
- var list = itemList()
- return list.length ? valueOf(list[0], 0) : ""
+ return defaultValue()
}
// The selection this instance started with. The runtime falls back to it
@@ -67,34 +75,6 @@ component Tabs {
return orientation === "vertical" ? "vertical" : "horizontal"
}
- // Back and forward arrive here. The runtime announces the value the URL
- // now names; applying it must not write history, or stepping back would
- // push a new entry and trap the user.
- client function applyUrlValue(sourceEvent) {
- var detail = sourceEvent ? sourceEvent.detail : null
- var value = detail ? detail.value : ""
- if (!value) {
- return
- }
- var list = itemList()
- var found = -1
- for (var index = 0; index < list.length; index += 1) {
- if (valueOf(list[index], index) === value) {
- found = index
- }
- }
- if (found === -1) {
- return
- }
- // Idempotent against our own state rather than a DOM attribute the
- // re-render owns: popstate can fire for a value already selected.
- if (value === currentValue()) {
- return
- }
- activeValue = value
- output.change({ value: value, item: list[found], index: found })
- }
-
client function selectTab(item, index, sourceEvent) {
if (item.disabled) {
return
@@ -123,9 +103,6 @@ component Tabs {
data-size='{size}'
data-mode='{mode}'
data-param='{param}'
- data-wrn-tabs-param='{mode === "url" ? param : ""}'
- data-wrn-tabs-default='{defaultValue()}'
- @wrnexus:tabs:restore='applyUrlValue(event)'
>
{
const root = dom.querySelector(".wire-tabs") as HTMLElement;
expect(root.getAttribute("data-mode")).toBe("url");
expect(root.getAttribute("data-param")).toBe("tab");
+
+ /*
+ * In url mode the query parameter is the source of truth rather than
+ * component state. The client router owns popstate and swaps the whole page
+ * on back and forward, discarding component state, so the selection has to
+ * fall out of the URL for history to work at all.
+ */
+ const source2 = readFileSync(uiComponentPath("Tabs"), "utf8");
+ expect(source2).toContain("URLSearchParams(window.location.search)");
+ expect(source2).toContain("history.pushState");
});
test("tabs vertical orientation switches the roving axis", async () => {