@picked/svelte 0.0.1-dev.1789541668638 → 0.0.1-dev.1793753513002
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/LICENSE +1 -0
- package/README.md +51 -0
- package/dist/.tsbuildinfo +1 -1
- package/dist/_virtual/_vite/preload-helper.js +1 -1
- package/dist/components/index.js +1 -1
- package/dist/experience/ExperienceHost.svelte +29 -13
- package/dist/experience/ExperienceSlot.svelte +21 -3
- package/dist/experience/ExperienceWrapper.svelte +23 -0
- package/dist/form/index.js +1 -1
- package/dist/hooks/action.d.ts +47 -18
- package/dist/hooks/action.js +1 -1
- package/dist/hooks/index.d.ts +2 -2
- package/dist/hooks/index.js +1 -1
- package/dist/hooks/step.d.ts +14 -0
- package/dist/hooks/step.js +1 -1
- package/dist/plugins/devtools/DevToolsExperience.svelte +545 -203
- package/dist/plugins/devtools/components/AdaptedToolHost.svelte +2 -2
- package/dist/plugins/devtools/constants.d.ts +5 -0
- package/dist/plugins/devtools/constants.js +1 -0
- package/dist/plugins/devtools/customTools.d.ts +42 -6
- package/dist/plugins/devtools/customTools.js +1 -1
- package/dist/plugins/devtools/derive/answer-display/answer-display.d.ts +112 -0
- package/dist/plugins/devtools/derive/answer-display/answer-display.js +1 -0
- package/dist/plugins/devtools/derive/answer-display/index.d.ts +3 -0
- package/dist/plugins/devtools/derive/answer-display/index.js +1 -0
- package/dist/plugins/devtools/derive/answer-history/answer-history.d.ts +76 -0
- package/dist/plugins/devtools/derive/answer-history/answer-history.js +1 -0
- package/dist/plugins/devtools/derive/answer-history/index.d.ts +3 -0
- package/dist/plugins/devtools/derive/answer-history/index.js +1 -0
- package/dist/plugins/devtools/derive/guide-sections/guide-sections.d.ts +67 -0
- package/dist/plugins/devtools/derive/guide-sections/guide-sections.js +1 -0
- package/dist/plugins/devtools/derive/guide-sections/index.d.ts +3 -0
- package/dist/plugins/devtools/derive/guide-sections/index.js +1 -0
- package/dist/plugins/devtools/derive/index.d.ts +28 -0
- package/dist/plugins/devtools/derive/index.js +1 -0
- package/dist/plugins/devtools/derive/journey-outline/index.d.ts +3 -0
- package/dist/plugins/devtools/derive/journey-outline/index.js +1 -0
- package/dist/plugins/devtools/derive/journey-outline/journey-outline.d.ts +86 -0
- package/dist/plugins/devtools/derive/journey-outline/journey-outline.js +1 -0
- package/dist/plugins/devtools/derive/journey-pick/index.d.ts +3 -0
- package/dist/plugins/devtools/derive/journey-pick/index.js +1 -0
- package/dist/plugins/devtools/derive/journey-pick/journey-pick.d.ts +73 -0
- package/dist/plugins/devtools/derive/journey-pick/journey-pick.js +1 -0
- package/dist/plugins/devtools/derive/journey-timeline/index.d.ts +3 -0
- package/dist/plugins/devtools/derive/journey-timeline/index.js +1 -0
- package/dist/plugins/devtools/derive/journey-timeline/journey-timeline.d.ts +70 -0
- package/dist/plugins/devtools/derive/journey-timeline/journey-timeline.js +1 -0
- package/dist/plugins/devtools/derive/journey-tree/index.d.ts +3 -0
- package/dist/plugins/devtools/derive/journey-tree/index.js +1 -0
- package/dist/plugins/devtools/derive/journey-tree/journey-tree.d.ts +191 -0
- package/dist/plugins/devtools/derive/journey-tree/journey-tree.js +1 -0
- package/dist/plugins/devtools/derive/plain-language/index.d.ts +3 -0
- package/dist/plugins/devtools/derive/plain-language/index.js +1 -0
- package/dist/plugins/devtools/derive/plain-language/plain-language.d.ts +73 -0
- package/dist/plugins/devtools/derive/plain-language/plain-language.js +1 -0
- package/dist/plugins/devtools/derive/plain-logic/index.d.ts +3 -0
- package/dist/plugins/devtools/derive/plain-logic/index.js +1 -0
- package/dist/plugins/devtools/derive/plain-logic/plain-logic.d.ts +48 -0
- package/dist/plugins/devtools/derive/plain-logic/plain-logic.js +1 -0
- package/dist/plugins/devtools/derive/safety-posture/index.d.ts +3 -0
- package/dist/plugins/devtools/derive/safety-posture/index.js +1 -0
- package/dist/plugins/devtools/derive/safety-posture/safety-posture.d.ts +43 -0
- package/dist/plugins/devtools/derive/safety-posture/safety-posture.js +1 -0
- package/dist/plugins/devtools/derive/visited-steps/index.d.ts +3 -0
- package/dist/plugins/devtools/derive/visited-steps/index.js +1 -0
- package/dist/plugins/devtools/derive/visited-steps/visited-steps.d.ts +35 -0
- package/dist/plugins/devtools/derive/visited-steps/visited-steps.js +1 -0
- package/dist/plugins/devtools/derive/walk-route/index.d.ts +3 -0
- package/dist/plugins/devtools/derive/walk-route/index.js +1 -0
- package/dist/plugins/devtools/derive/walk-route/walk-route.d.ts +22 -0
- package/dist/plugins/devtools/derive/walk-route/walk-route.js +1 -0
- package/dist/plugins/devtools/index.d.ts +5 -2
- package/dist/plugins/devtools/index.js +1 -1
- package/dist/plugins/devtools/launcher/Launcher.svelte +150 -0
- package/dist/plugins/devtools/launcher/launcher.d.ts +36 -0
- package/dist/plugins/devtools/launcher/launcher.js +1 -0
- package/dist/plugins/devtools/options.d.ts +106 -0
- package/dist/plugins/devtools/options.js +1 -0
- package/dist/plugins/devtools/plugin.d.ts +8 -8
- package/dist/plugins/devtools/plugin.js +1 -1
- package/dist/plugins/devtools/posture-context.d.ts +15 -0
- package/dist/plugins/devtools/posture-context.js +1 -0
- package/dist/plugins/devtools/review/answers/AnswersPanel.svelte +526 -0
- package/dist/plugins/devtools/review/guide/GuideLine.svelte +20 -0
- package/dist/plugins/devtools/review/guide/GuidePanel.svelte +133 -0
- package/dist/plugins/devtools/review/index.d.ts +6 -0
- package/dist/plugins/devtools/review/index.js +1 -0
- package/dist/plugins/devtools/review/journey/JourneyMap.svelte +1076 -0
- package/dist/plugins/devtools/review/journey/JourneyPanel.svelte +584 -0
- package/dist/plugins/devtools/review/journey/JourneyTimeline.svelte +750 -0
- package/dist/plugins/devtools/review/journey/TimelineMarker.svelte +59 -0
- package/dist/plugins/devtools/review/session/SessionPanel.svelte +214 -0
- package/dist/plugins/devtools/review/variations/VariationsPanel.svelte +279 -0
- package/dist/plugins/devtools/screens/config/ConfigScreen.svelte +6 -0
- package/dist/plugins/devtools/screens/custom-tools/CustomToolsScreen.svelte +3 -3
- package/dist/plugins/devtools/screens/responses/ResponseTableRow.svelte +6 -0
- package/dist/plugins/devtools/session/index.d.ts +3 -0
- package/dist/plugins/devtools/session/index.js +1 -0
- package/dist/plugins/devtools/session/session.d.ts +90 -0
- package/dist/plugins/devtools/session/session.js +1 -0
- package/dist/plugins/devtools/session-access.d.ts +32 -0
- package/dist/plugins/devtools/session-access.js +1 -0
- package/dist/plugins/devtools/sidebar/Sidebar.svelte +105 -0
- package/dist/plugins/devtools/sidebar/SidebarRow.svelte +117 -0
- package/dist/plugins/devtools/sidebar/nav.d.ts +105 -0
- package/dist/plugins/devtools/sidebar/nav.js +1 -0
- package/dist/plugins/devtools/ui/actions/Button.svelte +10 -1
- package/dist/plugins/devtools/ui/actions/CopyButton.svelte +13 -1
- package/dist/plugins/devtools/ui/data/Badge.svelte +3 -2
- package/dist/plugins/devtools/ui/data/EmptyState.svelte +16 -1
- package/dist/plugins/devtools/ui/feedback/Banner.svelte +3 -2
- package/dist/plugins/framework/index.js +1 -1
- package/dist/render/browser.js +1 -1
- package/package.json +23 -12
- package/dist/form/__tests__/CreateFormHarness.svelte +0 -30
- package/dist/hooks/events.d.ts +0 -2
- package/dist/hooks/events.js +0 -1
- package/dist/vite-env.d.js +0 -0
|
@@ -6,172 +6,415 @@
|
|
|
6
6
|
the change in the paired React component to maintain visual and behavioral parity.
|
|
7
7
|
-->
|
|
8
8
|
<script lang="ts">
|
|
9
|
-
|
|
10
|
-
import {
|
|
11
|
-
import
|
|
12
|
-
import
|
|
13
|
-
import
|
|
14
|
-
import
|
|
15
|
-
import
|
|
16
|
-
import
|
|
9
|
+
/** @file The devtools shell: the floating button, and a panel of tabs listed down its left-hand side. */
|
|
10
|
+
import type { Component } from 'svelte'
|
|
11
|
+
import { tick } from 'svelte'
|
|
12
|
+
import { getExperience, getPicked, useUnit } from '../../hooks'
|
|
13
|
+
import { Badge, Button, Heading, Switch } from './ui'
|
|
14
|
+
import PickedIcon from './PickedIcon.svelte'
|
|
15
|
+
import AdaptedToolHost from './components/AdaptedToolHost.svelte'
|
|
16
|
+
import Launcher from './launcher/Launcher.svelte'
|
|
17
|
+
import { readAutoHide, writeAutoHide } from './launcher/launcher'
|
|
18
|
+
import Sidebar from './sidebar/Sidebar.svelte'
|
|
19
|
+
import {
|
|
20
|
+
DEVELOPER_OPEN_STORAGE_KEY,
|
|
21
|
+
TAB_STORAGE_KEY,
|
|
22
|
+
buildNav,
|
|
23
|
+
developerTabId,
|
|
24
|
+
isInDeveloper,
|
|
25
|
+
readStored,
|
|
26
|
+
resolveTab,
|
|
27
|
+
toolTabId,
|
|
28
|
+
writeStored,
|
|
29
|
+
} from './sidebar/nav'
|
|
30
|
+
import { readDevtoolsOptions } from './options'
|
|
31
|
+
import { readCustomTools, reviewTools } from './customTools'
|
|
32
|
+
import { provideDevtoolsSession } from './session-access'
|
|
33
|
+
import { setPostureContext } from './posture-context'
|
|
34
|
+
import { createSessionData } from './session'
|
|
35
|
+
import {
|
|
36
|
+
TAB_INTROS,
|
|
37
|
+
configSourceLabel,
|
|
38
|
+
derivePosture,
|
|
39
|
+
guideFixes,
|
|
40
|
+
guideSections,
|
|
41
|
+
type GuideSection,
|
|
42
|
+
type ReviewTabId,
|
|
43
|
+
} from './derive'
|
|
44
|
+
import { AnswersPanel, GuidePanel, JourneyPanel, SessionPanel, VariationsPanel } from './review'
|
|
17
45
|
import ProjectScreen from './screens/project/ProjectScreen.svelte'
|
|
46
|
+
import ComponentsListScreen from './screens/components/ComponentsListScreen.svelte'
|
|
18
47
|
import ConfigScreen from './screens/config/ConfigScreen.svelte'
|
|
19
|
-
import IdentityScreen from './screens/identity/IdentityScreen.svelte'
|
|
20
|
-
import HistoryEntriesListScreen from './screens/history/HistoryEntriesListScreen.svelte'
|
|
21
|
-
import ResponsesListScreen from './screens/responses/ResponsesListScreen.svelte'
|
|
22
|
-
import FieldsListScreen from './screens/fields/FieldsListScreen.svelte'
|
|
23
48
|
import ContentListScreen from './screens/content/ContentListScreen.svelte'
|
|
24
|
-
import FeaturesListScreen from './screens/features/FeaturesListScreen.svelte'
|
|
25
49
|
import EventsListScreen from './screens/events/EventsListScreen.svelte'
|
|
50
|
+
import ExperienceInstancesListScreen from './screens/experience-instances/ExperienceInstancesListScreen.svelte'
|
|
51
|
+
import ExperiencesListScreen from './screens/experiences/ExperiencesListScreen.svelte'
|
|
52
|
+
import FeaturesListScreen from './screens/features/FeaturesListScreen.svelte'
|
|
53
|
+
import FieldsListScreen from './screens/fields/FieldsListScreen.svelte'
|
|
54
|
+
import FrameworksListScreen from './screens/frameworks/FrameworksListScreen.svelte'
|
|
55
|
+
import HistoryEntriesListScreen from './screens/history/HistoryEntriesListScreen.svelte'
|
|
56
|
+
import IdentityScreen from './screens/identity/IdentityScreen.svelte'
|
|
57
|
+
import PredicatesListScreen from './screens/predicates/PredicatesListScreen.svelte'
|
|
58
|
+
import ResponsesListScreen from './screens/responses/ResponsesListScreen.svelte'
|
|
26
59
|
import CustomToolsScreen from './screens/custom-tools/CustomToolsScreen.svelte'
|
|
27
|
-
import PickedIcon from './PickedIcon.svelte'
|
|
28
60
|
|
|
29
61
|
const experience = getExperience()
|
|
30
62
|
const client = getPicked()
|
|
31
63
|
|
|
64
|
+
const options = readDevtoolsOptions(client)
|
|
65
|
+
const tools = reviewTools(readCustomTools(client))
|
|
66
|
+
const posture = derivePosture({
|
|
67
|
+
readOnly: options.readOnly,
|
|
68
|
+
environmentLabel: options.environmentLabel,
|
|
69
|
+
})
|
|
70
|
+
setPostureContext(posture)
|
|
71
|
+
|
|
32
72
|
const dashboardProjectUrl = `${client.dashboardUrl}/p/${client.projectId}`
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
-
const
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
73
|
+
const previewToken = client.searchParams['preview-token']?.[0]
|
|
74
|
+
const preview = previewTag()
|
|
75
|
+
const configSource = configSourceLabel(previewToken)
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* The tag beside the floating button: a preview session, a preview whose workspace is gone, or none.
|
|
79
|
+
*
|
|
80
|
+
* @returns {'preview' | 'unavailable' | null} The tag to show.
|
|
81
|
+
* @example previewTag() // 'preview' with a preview token
|
|
82
|
+
*/
|
|
83
|
+
function previewTag(): 'preview' | 'unavailable' | null {
|
|
84
|
+
if (client.previewUnavailable) return 'unavailable'
|
|
85
|
+
return previewToken ? 'preview' : null
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const sessionStorageOf = (): Storage => window.sessionStorage
|
|
89
|
+
const localStorageOf = (): Storage => window.localStorage
|
|
90
|
+
|
|
91
|
+
/** The developer screens, keyed by their sidebar id. */
|
|
92
|
+
const DEVELOPER_COMPONENTS: Record<string, Component> = {
|
|
93
|
+
[developerTabId('project')]: ProjectScreen,
|
|
94
|
+
[developerTabId('components')]: ComponentsListScreen,
|
|
95
|
+
[developerTabId('config')]: ConfigScreen,
|
|
96
|
+
[developerTabId('content')]: ContentListScreen,
|
|
97
|
+
[developerTabId('events')]: EventsListScreen,
|
|
98
|
+
[developerTabId('experience-instances')]: ExperienceInstancesListScreen,
|
|
99
|
+
[developerTabId('experiences')]: ExperiencesListScreen,
|
|
100
|
+
[developerTabId('features')]: FeaturesListScreen,
|
|
101
|
+
[developerTabId('fields')]: FieldsListScreen,
|
|
102
|
+
[developerTabId('frameworks')]: FrameworksListScreen,
|
|
103
|
+
[developerTabId('history')]: HistoryEntriesListScreen,
|
|
104
|
+
[developerTabId('identity')]: IdentityScreen,
|
|
105
|
+
[developerTabId('predicates')]: PredicatesListScreen,
|
|
106
|
+
[developerTabId('responses')]: ResponsesListScreen,
|
|
107
|
+
[developerTabId('custom-tools')]: CustomToolsScreen,
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// ── The session record ───────────────────────────────────────────────────────────────────────
|
|
111
|
+
// Made at mount, which is launch: the launcher renders while the panel is closed, so the trail
|
|
112
|
+
// and the answer history listen from the start.
|
|
113
|
+
const session = createSessionData(client, {
|
|
114
|
+
exclude: options.hiddenExperienceKeys,
|
|
115
|
+
precedence: options.journeyPrecedence,
|
|
116
|
+
codeOwned: options.codeOwnedFields,
|
|
117
|
+
})
|
|
118
|
+
const releaseSession = provideDevtoolsSession(client, { session, posture })
|
|
119
|
+
$effect(() => () => {
|
|
120
|
+
releaseSession()
|
|
121
|
+
session.destroy()
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
const trails = useUnit(session.trailsStore)
|
|
125
|
+
const answerHistory = useUnit(session.historyStore)
|
|
126
|
+
const asked = useUnit(session.askedStore)
|
|
127
|
+
const journey = useUnit(session.journeyStore)
|
|
128
|
+
const features = useUnit(client.features.listStore)
|
|
129
|
+
const isOpen = useUnit(experience.isOpenStore)
|
|
130
|
+
|
|
131
|
+
// ── Tabs ─────────────────────────────────────────────────────────────────────────────────────
|
|
132
|
+
const nav = $derived(buildNav({ variations: $features.length > 0, tools }))
|
|
133
|
+
const storedTab = readStored(sessionStorageOf, TAB_STORAGE_KEY)
|
|
134
|
+
let tab = $state(storedTab ?? 'guide')
|
|
135
|
+
let developerOpen = $state(readStored(sessionStorageOf, DEVELOPER_OPEN_STORAGE_KEY) === 'true')
|
|
136
|
+
let drawerOpen = $state(false)
|
|
137
|
+
|
|
138
|
+
// A tab can disappear under the viewer (Variations once the feature list is empty), and a stored
|
|
139
|
+
// developer screen opens its group.
|
|
140
|
+
$effect(() => {
|
|
141
|
+
const resolved = resolveTab(tab, nav)
|
|
142
|
+
if (resolved !== tab) tab = resolved
|
|
143
|
+
if (isInDeveloper(tab, nav) && !developerOpen) developerOpen = true
|
|
144
|
+
})
|
|
145
|
+
|
|
146
|
+
const selectedItem = $derived([...nav.primary, ...nav.developer].find((item) => item.id === tab))
|
|
147
|
+
const selectedTool = $derived(tools.find((tool) => toolTabId(tool.id) === tab))
|
|
148
|
+
const developerScreen = $derived(DEVELOPER_COMPONENTS[tab])
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Narrows a tab id to one of the review tabs.
|
|
152
|
+
*
|
|
153
|
+
* @param {string} id - A sidebar tab id.
|
|
154
|
+
* @returns {boolean} `true` for a review tab.
|
|
155
|
+
* @example isReviewTab('answers') // true
|
|
156
|
+
*/
|
|
157
|
+
function isReviewTab(id: string): id is ReviewTabId {
|
|
158
|
+
return id in TAB_INTROS
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Selects a tab, remembers it, and closes the narrow-panel drawer.
|
|
163
|
+
*
|
|
164
|
+
* @param {string} id - The tab to select.
|
|
165
|
+
* @example selectTab('journey')
|
|
166
|
+
*/
|
|
167
|
+
function selectTab(id: string): void {
|
|
168
|
+
tab = id
|
|
169
|
+
drawerOpen = false
|
|
170
|
+
writeStored(sessionStorageOf, TAB_STORAGE_KEY, id)
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Opens or folds the Developer group, and remembers it.
|
|
175
|
+
*
|
|
176
|
+
* @example toggleDeveloper()
|
|
177
|
+
*/
|
|
178
|
+
function toggleDeveloper(): void {
|
|
179
|
+
developerOpen = !developerOpen
|
|
180
|
+
writeStored(sessionStorageOf, DEVELOPER_OPEN_STORAGE_KEY, String(developerOpen))
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// ── How to use ───────────────────────────────────────────────────────────────────────────────
|
|
184
|
+
const guide = $derived.by(() => {
|
|
185
|
+
const available = { variations: $features.length > 0 }
|
|
186
|
+
const lead: GuideSection[] = options.guide.sections.map((section) => ({ ...section, tab: null }))
|
|
187
|
+
const toolSections: GuideSection[] = tools.flatMap((tool) =>
|
|
188
|
+
tool.guide
|
|
189
|
+
? [
|
|
190
|
+
{
|
|
191
|
+
id: `tool-${tool.id}`,
|
|
192
|
+
title: tool.label,
|
|
193
|
+
steps: tool.guide.steps,
|
|
194
|
+
notes: tool.guide.notes ?? [],
|
|
195
|
+
tab: toolTabId(tool.id),
|
|
196
|
+
},
|
|
197
|
+
]
|
|
198
|
+
: [],
|
|
199
|
+
)
|
|
200
|
+
return {
|
|
201
|
+
sections: guideSections(available, { lead, tools: toolSections }),
|
|
202
|
+
fixes: guideFixes(available, options.guide.fixes),
|
|
203
|
+
tabLabels: Object.fromEntries(nav.primary.map((item) => [item.id, item.label])),
|
|
204
|
+
}
|
|
205
|
+
})
|
|
206
|
+
|
|
207
|
+
const followUp = options.answerFollowUp
|
|
208
|
+
? { label: options.answerFollowUp.label, run: () => options.answerFollowUp?.run(client) }
|
|
209
|
+
: null
|
|
210
|
+
|
|
211
|
+
// ── The floating button ──────────────────────────────────────────────────────────────────────
|
|
212
|
+
let autoHide = $state(readAutoHide(localStorageOf, options.launcher.autoHide))
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Records the viewer's auto-hide choice.
|
|
216
|
+
*
|
|
217
|
+
* @param {boolean} value - Whether the button auto-hides.
|
|
218
|
+
* @example setAutoHide(false)
|
|
219
|
+
*/
|
|
220
|
+
function setAutoHide(value: boolean): void {
|
|
221
|
+
autoHide = value
|
|
222
|
+
writeAutoHide(localStorageOf, value)
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// ── Dialog behaviour ─────────────────────────────────────────────────────────────────────────
|
|
226
|
+
/** The open panel; Svelte sets it back to `null` when the panel unmounts. */
|
|
227
|
+
let panel: HTMLDivElement | null = $state(null)
|
|
228
|
+
|
|
229
|
+
const FOCUSABLE =
|
|
230
|
+
'a[href],button:not([disabled]),input:not([disabled]),select:not([disabled]),textarea:not([disabled]),[tabindex]:not([tabindex="-1"])'
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* The panel's focusable elements, in document order.
|
|
234
|
+
*
|
|
235
|
+
* @returns {HTMLElement[]} Every focusable element inside the panel.
|
|
236
|
+
* @example focusables().at(0)?.focus()
|
|
237
|
+
*/
|
|
238
|
+
function focusables(): HTMLElement[] {
|
|
239
|
+
if (panel === null) return []
|
|
240
|
+
return Array.from(panel.querySelectorAll(FOCUSABLE)).filter(
|
|
241
|
+
(node): node is HTMLElement => node instanceof HTMLElement && node.offsetParent !== null,
|
|
242
|
+
)
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Keeps Tab inside the panel, and closes the drawer, then the panel, on Escape.
|
|
247
|
+
*
|
|
248
|
+
* @param {KeyboardEvent} event - A key pressed inside the panel.
|
|
249
|
+
* @example onPanelKeydown(new KeyboardEvent('keydown', { key: 'Escape' }))
|
|
250
|
+
*/
|
|
251
|
+
function onPanelKeydown(event: KeyboardEvent): void {
|
|
252
|
+
if (event.key === 'Escape') {
|
|
253
|
+
event.stopPropagation()
|
|
254
|
+
if (drawerOpen) drawerOpen = false
|
|
255
|
+
else experience.close()
|
|
256
|
+
return
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
if (event.key !== 'Tab') return
|
|
260
|
+
|
|
261
|
+
const nodes = focusables()
|
|
262
|
+
const first = nodes.at(0)
|
|
263
|
+
const last = nodes.at(-1)
|
|
264
|
+
if (first === undefined || last === undefined) return
|
|
265
|
+
if (event.shiftKey && event.target === first) {
|
|
266
|
+
event.preventDefault()
|
|
267
|
+
last.focus()
|
|
268
|
+
} else if (!event.shiftKey && event.target === last) {
|
|
269
|
+
event.preventDefault()
|
|
270
|
+
first.focus()
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
$effect(() => {
|
|
275
|
+
if (!$isOpen) return
|
|
276
|
+
void tick().then(() => focusables().at(0)?.focus())
|
|
277
|
+
})
|
|
129
278
|
</script>
|
|
130
279
|
|
|
131
280
|
{#if $isOpen}
|
|
132
|
-
<div
|
|
133
|
-
|
|
134
|
-
class="
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
281
|
+
<div data-testid="devtools" class="devtools">
|
|
282
|
+
<!-- The backdrop is a pointer target only; the keyboard closes the dialog with Escape. -->
|
|
283
|
+
<div class="backdrop" aria-hidden="true" onclick={() => experience.close()}></div>
|
|
284
|
+
<div
|
|
285
|
+
bind:this={panel}
|
|
286
|
+
class="container"
|
|
287
|
+
role="dialog"
|
|
288
|
+
aria-modal="true"
|
|
289
|
+
aria-labelledby="devtools-header"
|
|
290
|
+
tabindex="-1"
|
|
291
|
+
onkeydown={onPanelKeydown}
|
|
292
|
+
>
|
|
293
|
+
<header class="toolbar">
|
|
142
294
|
<div class="toolbar-left">
|
|
143
|
-
<
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
295
|
+
<button
|
|
296
|
+
type="button"
|
|
297
|
+
class="menu-button"
|
|
298
|
+
aria-expanded={drawerOpen}
|
|
299
|
+
aria-controls="devtools-nav"
|
|
300
|
+
data-testid="devtools-menu"
|
|
301
|
+
onclick={() => (drawerOpen = !drawerOpen)}
|
|
302
|
+
>
|
|
303
|
+
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true">
|
|
304
|
+
<path d="M2.5 4h11M2.5 8h11M2.5 12h11" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" />
|
|
305
|
+
</svg>
|
|
306
|
+
<span class="menu-label">{selectedItem?.label ?? 'Menu'}</span>
|
|
307
|
+
</button>
|
|
308
|
+
<span class="brand wide-only">
|
|
309
|
+
<PickedIcon height={22} width={22} />
|
|
310
|
+
<span id="devtools-header"><Heading size="small">Picked Devtools</Heading></span>
|
|
311
|
+
</span>
|
|
312
|
+
{#if posture.label !== null}
|
|
313
|
+
<Badge status={posture.readOnly ? 'warning' : 'default'} size="small" testId="devtools-posture">
|
|
314
|
+
{posture.label}
|
|
315
|
+
</Badge>
|
|
316
|
+
{/if}
|
|
317
|
+
<span class="config-source wide-only" data-testid="devtools-config-source">{configSource}</span>
|
|
147
318
|
</div>
|
|
148
|
-
<div class="toolbar-center"></div>
|
|
149
319
|
<div class="toolbar-right">
|
|
150
|
-
<
|
|
151
|
-
|
|
320
|
+
<span class="auto-hide" data-testid="devtools-auto-hide">
|
|
321
|
+
<Switch
|
|
322
|
+
label="Auto-hide button"
|
|
323
|
+
name="devtools-auto-hide"
|
|
324
|
+
size="small"
|
|
325
|
+
checked={autoHide}
|
|
326
|
+
onchange={setAutoHide}
|
|
327
|
+
/>
|
|
328
|
+
</span>
|
|
329
|
+
<span class="wide-only">
|
|
330
|
+
<Button variant="ghost" size="small" href={client.documentationSiteUrl} target="_blank">Docs</Button>
|
|
331
|
+
</span>
|
|
332
|
+
<span class="wide-only">
|
|
333
|
+
<Button variant="ghost" size="small" href={dashboardProjectUrl} target="_blank">Dashboard</Button>
|
|
334
|
+
</span>
|
|
152
335
|
<Button variant="ghost" size="small" onclick={() => experience.close()}>Close</Button>
|
|
153
336
|
</div>
|
|
154
337
|
</header>
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
338
|
+
|
|
339
|
+
<div class="body">
|
|
340
|
+
{#if drawerOpen}
|
|
341
|
+
<div class="drawer-scrim" aria-hidden="true" onclick={() => (drawerOpen = false)}></div>
|
|
342
|
+
{/if}
|
|
343
|
+
<div class="nav-column" class:drawer-open={drawerOpen} id="devtools-nav">
|
|
344
|
+
<Sidebar
|
|
345
|
+
{nav}
|
|
346
|
+
selected={tab}
|
|
347
|
+
{developerOpen}
|
|
348
|
+
onSelect={selectTab}
|
|
349
|
+
onToggleDeveloper={toggleDeveloper}
|
|
350
|
+
/>
|
|
351
|
+
</div>
|
|
352
|
+
|
|
353
|
+
<section class="pane" aria-label={selectedItem?.label} data-testid="devtools-pane">
|
|
354
|
+
{#if developerScreen}
|
|
355
|
+
{@const Screen = developerScreen}
|
|
356
|
+
<div class="developer-body">
|
|
357
|
+
<Screen />
|
|
358
|
+
</div>
|
|
359
|
+
{:else if selectedTool}
|
|
360
|
+
<div class="pane-header">
|
|
361
|
+
<h2 class="pane-title">{selectedTool.label}</h2>
|
|
362
|
+
{#if selectedTool.description}
|
|
363
|
+
<p class="intro">{selectedTool.description}</p>
|
|
364
|
+
{/if}
|
|
365
|
+
</div>
|
|
366
|
+
<div class="tool-body">
|
|
367
|
+
<AdaptedToolHost tool={selectedTool} />
|
|
368
|
+
</div>
|
|
369
|
+
{:else if isReviewTab(tab)}
|
|
370
|
+
<div class="pane-header">
|
|
371
|
+
<h2 class="pane-title">{selectedItem?.label}</h2>
|
|
372
|
+
{#if tab !== 'journey'}
|
|
373
|
+
<p class="intro">{TAB_INTROS[tab]}</p>
|
|
374
|
+
{/if}
|
|
375
|
+
</div>
|
|
376
|
+
<div class="review-body" class:fill={tab === 'journey'}>
|
|
377
|
+
{#if tab === 'guide'}
|
|
378
|
+
<GuidePanel
|
|
379
|
+
sections={guide.sections}
|
|
380
|
+
fixes={guide.fixes}
|
|
381
|
+
tabLabels={guide.tabLabels}
|
|
382
|
+
onOpenTab={selectTab}
|
|
383
|
+
/>
|
|
384
|
+
{:else if tab === 'session'}
|
|
385
|
+
<SessionPanel
|
|
386
|
+
{client}
|
|
387
|
+
{posture}
|
|
388
|
+
journey={$journey}
|
|
389
|
+
history={$answerHistory}
|
|
390
|
+
onShowAnswers={() => selectTab('answers')}
|
|
391
|
+
/>
|
|
392
|
+
{:else if tab === 'answers'}
|
|
393
|
+
<AnswersPanel {client} {posture} asked={$asked} {followUp} />
|
|
394
|
+
{:else if tab === 'journey'}
|
|
395
|
+
<JourneyPanel
|
|
396
|
+
{client}
|
|
397
|
+
{posture}
|
|
398
|
+
journey={$journey}
|
|
399
|
+
trails={$trails}
|
|
400
|
+
hiddenKeys={options.hiddenExperienceKeys}
|
|
401
|
+
/>
|
|
402
|
+
{:else if tab === 'variations'}
|
|
403
|
+
<VariationsPanel {client} />
|
|
404
|
+
{/if}
|
|
405
|
+
</div>
|
|
406
|
+
{/if}
|
|
407
|
+
</section>
|
|
408
|
+
</div>
|
|
158
409
|
</div>
|
|
159
410
|
</div>
|
|
160
411
|
{:else}
|
|
161
|
-
<
|
|
162
|
-
{
|
|
163
|
-
|
|
164
|
-
{
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
class:has-preview-tag={isPreview}
|
|
168
|
-
onclick={() => experience.open()}
|
|
169
|
-
aria-label="Open Picked Devtools"
|
|
170
|
-
title="Open Picked Devtools"
|
|
171
|
-
>
|
|
172
|
-
<PickedIcon height={25} width={25}/>
|
|
173
|
-
</button>
|
|
174
|
-
</div>
|
|
412
|
+
<Launcher
|
|
413
|
+
position={options.launcher.position}
|
|
414
|
+
{autoHide}
|
|
415
|
+
{preview}
|
|
416
|
+
onOpen={() => experience.open()}
|
|
417
|
+
/>
|
|
175
418
|
{/if}
|
|
176
419
|
|
|
177
420
|
<style>
|
|
@@ -264,6 +507,12 @@
|
|
|
264
507
|
overflow: hidden;
|
|
265
508
|
}
|
|
266
509
|
|
|
510
|
+
@media (max-width: 640px) {
|
|
511
|
+
.devtools {
|
|
512
|
+
padding: var(--space-2);
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
|
|
267
516
|
.backdrop {
|
|
268
517
|
position: fixed;
|
|
269
518
|
inset: 0;
|
|
@@ -281,44 +530,188 @@
|
|
|
281
530
|
z-index: 2;
|
|
282
531
|
width: 100%;
|
|
283
532
|
height: 100%;
|
|
533
|
+
container-type: inline-size;
|
|
534
|
+
container-name: devtools-panel;
|
|
284
535
|
}
|
|
285
536
|
|
|
286
537
|
.toolbar {
|
|
287
|
-
height: 40px;
|
|
538
|
+
min-height: 40px;
|
|
288
539
|
background-color: var(--color-white);
|
|
289
540
|
border-bottom: 1px solid var(--color-gray-200);
|
|
290
541
|
padding: 0 var(--space-4);
|
|
291
542
|
display: flex;
|
|
292
543
|
align-items: center;
|
|
293
544
|
justify-content: space-between;
|
|
545
|
+
gap: var(--space-3);
|
|
294
546
|
flex-shrink: 0;
|
|
295
547
|
}
|
|
296
548
|
|
|
297
549
|
.toolbar-left {
|
|
298
550
|
display: flex;
|
|
299
551
|
align-items: center;
|
|
552
|
+
gap: var(--space-2);
|
|
553
|
+
min-width: 0;
|
|
300
554
|
}
|
|
301
555
|
|
|
302
|
-
.toolbar-
|
|
556
|
+
.toolbar-right {
|
|
303
557
|
display: flex;
|
|
304
558
|
align-items: center;
|
|
305
|
-
|
|
559
|
+
gap: var(--space-1);
|
|
560
|
+
flex-shrink: 0;
|
|
306
561
|
}
|
|
307
562
|
|
|
308
|
-
.
|
|
563
|
+
.brand {
|
|
309
564
|
display: flex;
|
|
310
565
|
align-items: center;
|
|
311
|
-
gap: var(--space-
|
|
566
|
+
gap: var(--space-2);
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
.config-source {
|
|
570
|
+
font-size: var(--text-xs);
|
|
571
|
+
color: var(--color-gray-500);
|
|
572
|
+
white-space: nowrap;
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
.auto-hide {
|
|
576
|
+
margin-right: var(--space-2);
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
.menu-button {
|
|
580
|
+
display: none;
|
|
581
|
+
align-items: center;
|
|
582
|
+
gap: var(--space-2);
|
|
583
|
+
min-width: 0;
|
|
584
|
+
height: 28px;
|
|
585
|
+
padding: 0 var(--space-2);
|
|
586
|
+
border: 1px solid var(--color-gray-200);
|
|
587
|
+
border-radius: var(--radius-md);
|
|
588
|
+
background-color: var(--color-white);
|
|
589
|
+
font: inherit;
|
|
590
|
+
font-size: var(--text-sm);
|
|
591
|
+
font-weight: var(--font-medium);
|
|
592
|
+
color: var(--color-gray-900);
|
|
593
|
+
cursor: pointer;
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
.menu-button:focus-visible {
|
|
597
|
+
outline: var(--ring-width) solid var(--ring-color);
|
|
598
|
+
outline-offset: var(--ring-offset);
|
|
312
599
|
}
|
|
313
600
|
|
|
314
|
-
.
|
|
601
|
+
.menu-label {
|
|
602
|
+
overflow: hidden;
|
|
603
|
+
text-overflow: ellipsis;
|
|
604
|
+
white-space: nowrap;
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
.body {
|
|
608
|
+
position: relative;
|
|
315
609
|
flex: 1;
|
|
316
|
-
|
|
317
|
-
|
|
610
|
+
display: grid;
|
|
611
|
+
grid-template-columns: 200px minmax(0, 1fr);
|
|
612
|
+
min-height: 0;
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
.nav-column {
|
|
616
|
+
min-height: 0;
|
|
617
|
+
border-right: 1px solid var(--color-gray-200);
|
|
618
|
+
background-color: var(--color-white);
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
.drawer-scrim {
|
|
622
|
+
display: none;
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
.pane {
|
|
626
|
+
display: flex;
|
|
627
|
+
flex-direction: column;
|
|
628
|
+
min-width: 0;
|
|
629
|
+
min-height: 0;
|
|
630
|
+
overflow: hidden;
|
|
631
|
+
background-color: var(--color-white);
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
.pane-header {
|
|
635
|
+
flex-shrink: 0;
|
|
636
|
+
padding: var(--space-3) var(--space-4) 0;
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
.pane-title {
|
|
640
|
+
margin: 0;
|
|
641
|
+
font-size: var(--text-base);
|
|
642
|
+
font-weight: var(--font-semibold);
|
|
643
|
+
color: var(--color-gray-900);
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
.intro {
|
|
647
|
+
margin: var(--space-1) 0 0;
|
|
648
|
+
font-size: var(--text-xs);
|
|
649
|
+
color: var(--color-gray-500);
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
.review-body {
|
|
653
|
+
flex: 1;
|
|
654
|
+
min-height: 0;
|
|
655
|
+
overflow-y: auto;
|
|
656
|
+
padding: var(--space-3) var(--space-4) var(--space-4);
|
|
657
|
+
scrollbar-width: thin;
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
.review-body.fill {
|
|
318
661
|
display: flex;
|
|
319
662
|
flex-direction: column;
|
|
320
663
|
}
|
|
321
664
|
|
|
665
|
+
.tool-body {
|
|
666
|
+
flex: 1;
|
|
667
|
+
min-height: 0;
|
|
668
|
+
overflow: auto;
|
|
669
|
+
display: grid;
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
.developer-body {
|
|
673
|
+
flex: 1 1 auto;
|
|
674
|
+
display: grid;
|
|
675
|
+
min-height: 0;
|
|
676
|
+
overflow: auto;
|
|
677
|
+
background-color: var(--color-gray-50);
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
@container devtools-panel (max-width: 639px) {
|
|
681
|
+
.body {
|
|
682
|
+
grid-template-columns: minmax(0, 1fr);
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
.nav-column {
|
|
686
|
+
display: none;
|
|
687
|
+
position: absolute;
|
|
688
|
+
inset: 0 auto 0 0;
|
|
689
|
+
z-index: 3;
|
|
690
|
+
width: min(280px, 85%);
|
|
691
|
+
box-shadow: var(--shadow-xl);
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
.nav-column.drawer-open {
|
|
695
|
+
display: block;
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
.drawer-scrim {
|
|
699
|
+
display: block;
|
|
700
|
+
position: absolute;
|
|
701
|
+
inset: 0;
|
|
702
|
+
z-index: 2;
|
|
703
|
+
background-color: rgba(0, 0, 0, 0.3);
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
.menu-button {
|
|
707
|
+
display: inline-flex;
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
.wide-only {
|
|
711
|
+
display: none;
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
|
|
322
715
|
/* Global table styles for screens that still use raw tables */
|
|
323
716
|
.devtools :global(table) {
|
|
324
717
|
width: 100%;
|
|
@@ -349,57 +742,6 @@
|
|
|
349
742
|
padding-right: var(--space-4);
|
|
350
743
|
}
|
|
351
744
|
|
|
352
|
-
.launcher {
|
|
353
|
-
position: fixed;
|
|
354
|
-
bottom: 1rem;
|
|
355
|
-
right: 0;
|
|
356
|
-
z-index: 999999;
|
|
357
|
-
display: flex;
|
|
358
|
-
align-items: stretch;
|
|
359
|
-
cursor: pointer;
|
|
360
|
-
transform: translateX(calc(100% - 8px));
|
|
361
|
-
transition: transform 180ms ease-out;
|
|
362
|
-
|
|
363
|
-
&:hover,
|
|
364
|
-
&:focus-within {
|
|
365
|
-
transform: translateX(0);
|
|
366
|
-
}
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
.floating-button {
|
|
370
|
-
border-radius: 6px;
|
|
371
|
-
background-color: #ffffff;
|
|
372
|
-
display: grid;
|
|
373
|
-
place-items: center;
|
|
374
|
-
cursor: pointer;
|
|
375
|
-
border: 0 transparent;
|
|
376
|
-
padding: 6px;
|
|
377
|
-
box-shadow: 2px 2px 4px #00000029;
|
|
378
|
-
|
|
379
|
-
&:hover {
|
|
380
|
-
background-color: #f5f5f5;
|
|
381
|
-
}
|
|
382
|
-
}
|
|
383
|
-
|
|
384
|
-
.floating-button.has-preview-tag {
|
|
385
|
-
border-radius: 0 6px 6px 0;
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
.preview-tag {
|
|
389
|
-
display: flex;
|
|
390
|
-
align-items: center;
|
|
391
|
-
padding: 0 10px;
|
|
392
|
-
background: #2563eb;
|
|
393
|
-
color: #ffffff;
|
|
394
|
-
border-radius: 6px 0 0 6px;
|
|
395
|
-
font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
|
|
396
|
-
'Helvetica Neue', Arial, sans-serif;
|
|
397
|
-
font-size: 12px;
|
|
398
|
-
font-weight: 500;
|
|
399
|
-
line-height: 1.4;
|
|
400
|
-
box-shadow: 2px 2px 4px #00000029;
|
|
401
|
-
}
|
|
402
|
-
|
|
403
745
|
/** Compact inline form elements for use within table cells */
|
|
404
746
|
.devtools :global(.inline-select),
|
|
405
747
|
.devtools :global(.inline-input) {
|