@north-light/crouter 0.3.197 → 0.3.198
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/dist/api/client.d.ts +2 -2
- package/dist/api/dto/inbox.d.ts +46 -0
- package/dist/builtin-memory/00-runtime-base.md +3 -3
- package/dist/builtin-memory/internal/nodes-and-canvas.md +1 -1
- package/dist/builtin-memory/internal/storage-tiers.md +1 -1
- package/dist/builtin-memory/wedged-child-on-runaway-bash.md +1 -1
- package/dist/clients/attach/render/chat-view.d.ts +3 -8
- package/dist/clients/attach/render/chat-view.js +3 -24
- package/dist/clients/attach/render/markdown-source.js +0 -6
- package/dist/clients/attach/render/tool-calls.js +16 -2
- package/dist/clients/attach/viewer.js +613 -602
- package/dist/clients/inbox/__tests__/serial/inbox-controller.test.js +2 -2
- package/dist/clients/inbox/__tests__/serial/mount-panel.test.js +11 -4
- package/dist/clients/inbox/controller.js +4 -4
- package/dist/clients/inbox/page-adapter.d.ts +2 -1
- package/dist/clients/inbox/tui/render.js +46 -25
- package/dist/clients/inbox/tui/slots.d.ts +1 -3
- package/dist/clients/inbox/tui/slots.js +8 -37
- package/dist/clients/inbox/tui/types.d.ts +4 -3
- package/dist/commands/__tests__/human.test.js +160 -23
- package/dist/commands/canvas-browse.js +1 -1
- package/dist/commands/human/doc.js +21 -74
- package/dist/commands/human/html.d.ts +2 -0
- package/dist/commands/human/html.js +81 -0
- package/dist/commands/human/prompts.d.ts +5 -4
- package/dist/commands/human/prompts.js +81 -153
- package/dist/commands/human/shared.d.ts +5 -6
- package/dist/commands/human/shared.js +13 -61
- package/dist/commands/human.js +7 -2
- package/dist/core/__tests__/daemon-boot.test.js +7 -4
- package/dist/core/__tests__/dead-node-policy-table.test.js +20 -36
- package/dist/core/__tests__/human-deliver.test.js +28 -37
- package/dist/core/__tests__/seam/broker-crash-teardown.test.js +13 -11
- package/dist/core/__tests__/seam/dormancy-release.test.js +63 -4
- package/dist/core/__tests__/serial/human-deliver-e2e.test.js +3 -13
- package/dist/core/canvas/__tests__/attention.test.js +2 -2
- package/dist/core/canvas/__tests__/render-remote.test.js +2 -2
- package/dist/core/canvas/browse/app.d.ts +1 -1
- package/dist/core/canvas/browse/app.js +21 -3
- package/dist/core/canvas/browse/render.d.ts +4 -0
- package/dist/core/canvas/browse/render.js +5 -1
- package/dist/core/canvas/canvas.d.ts +26 -0
- package/dist/core/canvas/canvas.js +55 -0
- package/dist/core/command.js +6 -11
- package/dist/core/config.js +3 -2
- package/dist/core/help.d.ts +3 -0
- package/dist/core/human/__tests__/html-markdown.test.d.ts +1 -0
- package/dist/core/human/__tests__/html-markdown.test.js +52 -0
- package/dist/core/human/__tests__/page-catalog.test.d.ts +1 -0
- package/dist/core/human/__tests__/page-catalog.test.js +16 -0
- package/dist/core/human/__tests__/page-render.test.js +40 -23
- package/dist/core/human/__tests__/page-tickets.test.js +48 -27
- package/dist/core/human/__tests__/page.test.js +48 -65
- package/dist/core/human/__tests__/serial/inbox-core.test.js +5 -15
- package/dist/core/human/answer-text.d.ts +3 -0
- package/dist/core/human/answer-text.js +98 -0
- package/dist/core/human/answer.d.ts +51 -0
- package/dist/core/human/answer.js +125 -0
- package/dist/core/human/component-docs.d.ts +14 -0
- package/dist/core/human/component-docs.js +161 -0
- package/dist/core/human/html-markdown.d.ts +3 -0
- package/dist/core/human/html-markdown.js +288 -0
- package/dist/core/human/page-catalog.d.ts +19 -3
- package/dist/core/human/page-catalog.js +73 -16
- package/dist/core/human/page-render.d.ts +3 -2
- package/dist/core/human/page-render.js +19 -45
- package/dist/core/human/page-schema.d.ts +108 -8
- package/dist/core/human/page-schema.js +88 -31
- package/dist/core/human/page-synth.d.ts +2 -4
- package/dist/core/human/page-synth.js +23 -7
- package/dist/core/human/page.d.ts +14 -7
- package/dist/core/human/page.js +178 -90
- package/dist/core/human/review-schema.d.ts +1 -0
- package/dist/core/human/review-schema.js +1 -1
- package/dist/core/human/scan.d.ts +3 -2
- package/dist/core/human/scan.js +7 -8
- package/dist/core/human/summary.d.ts +4 -7
- package/dist/core/human/summary.js +4 -60
- package/dist/core/human/tickets.d.ts +10 -9
- package/dist/core/human/tickets.js +5 -3
- package/dist/core/human/types.d.ts +4 -0
- package/dist/core/keybindings/catalog.d.ts +2 -2
- package/dist/core/keybindings/catalog.js +1 -0
- package/dist/core/review/realize.js +9 -1
- package/dist/core/runtime/boot-root.js +6 -1
- package/dist/core/runtime/broker.js +8 -6
- package/dist/core/runtime/busy.d.ts +4 -3
- package/dist/core/runtime/busy.js +4 -3
- package/dist/core/runtime/spawn.js +4 -4
- package/dist/core/user-settings.d.ts +17 -2
- package/dist/core/user-settings.js +32 -11
- package/dist/daemon/api/handlers/human.js +1 -1
- package/dist/daemon/api/handlers/inbox.js +22 -7
- package/dist/daemon/cron-run.js +16 -2
- package/dist/daemon/crtrd.js +1 -1
- package/dist/daemon/fleet.d.ts +22 -10
- package/dist/daemon/fleet.js +43 -24
- package/dist/daemon/human/finish.d.ts +2 -1
- package/dist/daemon/human/finish.js +45 -28
- package/dist/daemon/human/sweep.js +4 -2
- package/dist/daemon/reconcilers/broker-supervision.js +11 -8
- package/dist/daemon/reconcilers/dormant-inbox.js +11 -6
- package/dist/daemon/reconcilers/live-obligation.d.ts +21 -1
- package/dist/daemon/reconcilers/live-obligation.js +30 -2
- package/dist/daemon/reconcilers/storage-maintenance.d.ts +5 -0
- package/dist/daemon/reconcilers/storage-maintenance.js +28 -1
- package/dist/daemon/review/sweep.js +12 -1
- package/dist/pages/bundle.css +1 -1
- package/dist/pages/bundle.js +1008 -577
- package/dist/pages/comments.d.ts +29 -0
- package/dist/pages/comments.js +51 -0
- package/dist/pages/controls.d.ts +98 -0
- package/dist/pages/controls.js +261 -0
- package/dist/pages/elements/cards.d.ts +15 -4
- package/dist/pages/elements/cards.js +299 -186
- package/dist/pages/elements/chart.d.ts +22 -0
- package/dist/pages/elements/chart.js +107 -91
- package/dist/pages/elements/options.d.ts +9 -0
- package/dist/pages/elements/options.js +268 -155
- package/dist/pages/elements/pages.d.ts +6 -0
- package/dist/pages/elements/pages.js +60 -38
- package/dist/pages/elements/slot.d.ts +5 -0
- package/dist/pages/elements/slot.js +17 -10
- package/dist/pages/elements/table.d.ts +17 -0
- package/dist/pages/elements/table.js +336 -228
- package/dist/pages/elements/text.d.ts +9 -3
- package/dist/pages/elements/text.js +350 -143
- package/dist/pages/host.d.ts +0 -24
- package/dist/pages/host.js +0 -17
- package/dist/pages/readonly.d.ts +24 -0
- package/dist/pages/readonly.js +31 -0
- package/dist/pages/responses.d.ts +37 -0
- package/dist/pages/responses.js +56 -0
- package/dist/pi-extensions/canvas-bash-valve.d.ts +9 -1
- package/dist/pi-extensions/canvas-bash-valve.js +12 -8
- package/dist/types.d.ts +19 -5
- package/dist/types.js +2 -1
- package/package.json +1 -1
- package/runtime.lock.json +2 -2
- package/scripts/install-runtime.mjs +19 -24
- package/dist/clients/attach/render/html-markdown.d.ts +0 -13
- package/dist/clients/attach/render/html-markdown.js +0 -206
- package/dist/core/human/markdown-html.d.ts +0 -8
- package/dist/core/human/markdown-html.js +0 -581
|
@@ -17,10 +17,22 @@
|
|
|
17
17
|
* Selection is kept in the agent's authored order, never click order, so the response reads
|
|
18
18
|
* the way the question did. `freetext` is absent rather than empty: the response says whether
|
|
19
19
|
* the user wrote something, not whether the box existed.
|
|
20
|
+
*
|
|
21
|
+
* Every styling value this element paints is read through a `--crtr-options-*` component
|
|
22
|
+
* token, so a host restyles the question without touching crtr. Paint that the bundle shares —
|
|
23
|
+
* the option controls, the two writing boxes, the focus ring on every button — chains through
|
|
24
|
+
* the `--crtr-control-*` family behind its element token, which is what keeps a comment box
|
|
25
|
+
* here identical to one in `<crtr-text>` unless a host deliberately parts them. Each anatomy
|
|
26
|
+
* part carries its own scope: the option row and the control inside it, the comment trigger
|
|
27
|
+
* and the box it opens, and the two buttons in that box are five things a person can point at,
|
|
28
|
+
* so they are five scopes rather than one shared name meaning two different rectangles.
|
|
20
29
|
*/
|
|
21
|
-
import { adoptComments, setComment, findComment } from '../comments.js';
|
|
30
|
+
import { adoptComments, canRecordComments, setComment, findComment } from '../comments.js';
|
|
31
|
+
import { controlStyle, focusRingStyle, writingBoxStyle } from '../controls.js';
|
|
22
32
|
import { respond, savedIds, savedResponse } from '../host.js';
|
|
33
|
+
import { applyReadOnly, isReadOnly } from '../readonly.js';
|
|
23
34
|
import { registerElement } from '../register.js';
|
|
35
|
+
import { registerSlotResponse } from '../responses.js';
|
|
24
36
|
import { readSlotConfig, readSlotId } from '../slot-config.js';
|
|
25
37
|
const BUBBLE_PLUS = '<svg viewBox="0 0 16 16" aria-hidden="true" focusable="false"><path d="M2.5 3.5h11v7h-6l-3 2.5v-2.5h-2z"/><path d="M8 5.5v3M6.5 7h3"/></svg>';
|
|
26
38
|
const BUBBLE_TEXT = '<svg viewBox="0 0 16 16" aria-hidden="true" focusable="false"><path d="M2.5 3.5h11v7h-6l-3 2.5v-2.5h-2z"/><path d="M5 6.25h6M5 8.25h4"/></svg>';
|
|
@@ -31,84 +43,118 @@ const STYLE = `
|
|
|
31
43
|
|
|
32
44
|
[part="container"] {
|
|
33
45
|
display: grid;
|
|
34
|
-
gap: var(--crtr-space-3);
|
|
46
|
+
gap: var(--crtr-options-container-gap, var(--crtr-space-3));
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/* The question reads as the agent's own prose above its control, matching the table's
|
|
50
|
+
caption rather than shouting over the options it introduces. */
|
|
51
|
+
[part="label"] {
|
|
52
|
+
margin: var(--crtr-options-label-margin, 0);
|
|
53
|
+
color: var(--crtr-options-label-text-color, var(--crtr-color-text-muted));
|
|
35
54
|
}
|
|
36
55
|
|
|
37
56
|
[part="list"] {
|
|
38
57
|
display: grid;
|
|
39
|
-
gap: var(--crtr-space-2);
|
|
58
|
+
gap: var(--crtr-options-list-gap, var(--crtr-space-2));
|
|
40
59
|
}
|
|
41
60
|
|
|
42
61
|
[part="option"] {
|
|
43
62
|
position: relative;
|
|
44
63
|
display: flex;
|
|
45
64
|
align-items: flex-start;
|
|
46
|
-
gap: var(--crtr-space-3);
|
|
47
|
-
padding: var(--crtr-space-2) var(--crtr-space-3);
|
|
48
|
-
background: var(--crtr-color-surface);
|
|
49
|
-
border:
|
|
50
|
-
|
|
51
|
-
|
|
65
|
+
gap: var(--crtr-options-option-gap, var(--crtr-space-3));
|
|
66
|
+
padding: var(--crtr-options-option-padding, var(--crtr-space-2) var(--crtr-space-3));
|
|
67
|
+
background: var(--crtr-options-option-fill, var(--crtr-color-surface));
|
|
68
|
+
border: var(--crtr-options-option-border-width, 1px)
|
|
69
|
+
var(--crtr-options-option-border-style, solid)
|
|
70
|
+
var(--crtr-options-option-border-color, var(--crtr-color-border));
|
|
71
|
+
border-radius: var(--crtr-options-option-radius, var(--crtr-radius-medium));
|
|
72
|
+
transition: background-color var(--crtr-options-option-transition, 120ms ease),
|
|
73
|
+
border-color var(--crtr-options-option-transition, 120ms ease);
|
|
52
74
|
}
|
|
53
75
|
|
|
54
76
|
[part="option"]:hover,
|
|
55
77
|
[part="option"][data-active="true"] {
|
|
56
|
-
background: var(--crtr-color-surface-hover);
|
|
78
|
+
background: var(--crtr-options-option-hover-fill, var(--crtr-color-surface-hover));
|
|
57
79
|
}
|
|
58
80
|
|
|
59
81
|
[part="option"][data-selected="true"] {
|
|
60
|
-
border-color: var(
|
|
61
|
-
|
|
82
|
+
border-color: var(
|
|
83
|
+
--crtr-options-option-selected-border-color,
|
|
84
|
+
color-mix(in srgb, var(--crtr-color-accent) 45%, transparent)
|
|
85
|
+
);
|
|
86
|
+
background: var(
|
|
87
|
+
--crtr-options-option-selected-fill,
|
|
88
|
+
color-mix(in srgb, var(--crtr-color-accent) 10%, transparent)
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
[part="option"][data-selected="true"]:hover,
|
|
93
|
+
[part="option"][data-selected="true"][data-active="true"] {
|
|
94
|
+
background: var(
|
|
95
|
+
--crtr-options-option-selected-hover-fill,
|
|
96
|
+
color-mix(in srgb, var(--crtr-color-accent) 14%, transparent)
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/* A page that can no longer take an answer keeps its selection legible and drops every
|
|
101
|
+
affordance that says it could still be changed. */
|
|
102
|
+
[part="option"]:has([part="option-control"]:disabled) [part="option-label"] {
|
|
103
|
+
cursor: default;
|
|
62
104
|
}
|
|
63
105
|
|
|
64
106
|
[part="option-label"] {
|
|
65
107
|
display: flex;
|
|
66
108
|
flex: 1;
|
|
67
109
|
align-items: flex-start;
|
|
68
|
-
gap: var(--crtr-space-3);
|
|
110
|
+
gap: var(--crtr-options-option-label-gap, var(--crtr-space-3));
|
|
69
111
|
cursor: pointer;
|
|
70
112
|
}
|
|
71
113
|
|
|
72
|
-
[part="option-control"]
|
|
73
|
-
margin: 0;
|
|
74
|
-
margin-top: 0.15em;
|
|
75
|
-
accent-color: var(--crtr-color-accent);
|
|
76
|
-
}
|
|
114
|
+
${controlStyle({ scope: 'options-option-control' }, '[part="option-control"]')}
|
|
77
115
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
116
|
+
/* Centred on the first line of the label rather than on its box, so a two-line option keeps
|
|
117
|
+
the control beside the title. Half the difference between a 1.5 line box and the control. */
|
|
118
|
+
[part="option-control"] {
|
|
119
|
+
margin-top: var(--crtr-options-option-control-offset, 0.1875em);
|
|
81
120
|
}
|
|
82
121
|
|
|
83
122
|
[part="option-text"] {
|
|
84
123
|
display: grid;
|
|
85
|
-
gap: var(--crtr-space-1);
|
|
124
|
+
gap: var(--crtr-options-option-text-gap, var(--crtr-space-1));
|
|
86
125
|
}
|
|
87
126
|
|
|
88
127
|
[part="option-title"] {
|
|
89
|
-
font-weight: 500;
|
|
128
|
+
font-weight: var(--crtr-options-option-title-font-weight, 500);
|
|
90
129
|
}
|
|
91
130
|
|
|
92
131
|
[part="option-description"] {
|
|
93
|
-
color: var(--crtr-color-text-muted);
|
|
94
|
-
font-size: 0.
|
|
132
|
+
color: var(--crtr-options-option-description-text-color, var(--crtr-color-text-muted));
|
|
133
|
+
font-size: var(--crtr-options-option-description-font-size, 0.875em);
|
|
95
134
|
}
|
|
96
135
|
|
|
136
|
+
/* The trigger stays in the row's anatomy while quiet, then its state rule below reveals it.
|
|
137
|
+
A host can choose how quiet and how bright those states are independently; the transition
|
|
138
|
+
remains one knob because all three properties are one reveal. */
|
|
97
139
|
[part="comment"] {
|
|
98
140
|
display: inline-flex;
|
|
99
141
|
align-items: center;
|
|
100
142
|
justify-content: center;
|
|
101
143
|
flex: none;
|
|
102
|
-
width: 1.
|
|
103
|
-
height: 1.
|
|
104
|
-
padding: 0;
|
|
105
|
-
color: var(--crtr-color-text-muted);
|
|
106
|
-
background: transparent;
|
|
107
|
-
border: 1px
|
|
108
|
-
|
|
109
|
-
|
|
144
|
+
width: var(--crtr-options-comment-size, 1.5em);
|
|
145
|
+
height: var(--crtr-options-comment-size, 1.5em);
|
|
146
|
+
padding: var(--crtr-options-comment-padding, 0);
|
|
147
|
+
color: var(--crtr-options-comment-text-color, var(--crtr-color-text-muted));
|
|
148
|
+
background: var(--crtr-options-comment-fill, transparent);
|
|
149
|
+
border: var(--crtr-options-comment-border-width, 1px)
|
|
150
|
+
var(--crtr-options-comment-border-style, solid)
|
|
151
|
+
var(--crtr-options-comment-border-color, transparent);
|
|
152
|
+
border-radius: var(--crtr-options-comment-radius, var(--crtr-radius-small));
|
|
153
|
+
opacity: var(--crtr-options-comment-opacity, 0);
|
|
110
154
|
cursor: pointer;
|
|
111
|
-
transition: opacity
|
|
155
|
+
transition: opacity var(--crtr-options-comment-transition, var(--crtr-control-transition, 120ms ease)),
|
|
156
|
+
color var(--crtr-options-comment-transition, var(--crtr-control-transition, 120ms ease)),
|
|
157
|
+
background-color var(--crtr-options-comment-transition, var(--crtr-control-transition, 120ms ease));
|
|
112
158
|
}
|
|
113
159
|
|
|
114
160
|
[part="option"]:hover [part="comment"],
|
|
@@ -116,158 +162,166 @@ const STYLE = `
|
|
|
116
162
|
[part="option"]:focus-within [part="comment"],
|
|
117
163
|
[part="comment"][data-state="commented"],
|
|
118
164
|
[part="comment"][aria-expanded="true"] {
|
|
119
|
-
opacity: 1;
|
|
165
|
+
opacity: var(--crtr-options-comment-visible-opacity, 1);
|
|
120
166
|
}
|
|
121
167
|
|
|
122
168
|
[part="comment"]:hover {
|
|
123
|
-
background: var(--crtr-color-surface-hover);
|
|
124
|
-
border-color: var(--crtr-color-border);
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
[part="comment"]:focus-visible {
|
|
128
|
-
outline: 2px solid var(--crtr-color-focus);
|
|
129
|
-
outline-offset: 1px;
|
|
169
|
+
background: var(--crtr-options-comment-hover-fill, var(--crtr-color-surface-hover));
|
|
170
|
+
border-color: var(--crtr-options-comment-hover-border-color, var(--crtr-color-border));
|
|
130
171
|
}
|
|
131
172
|
|
|
173
|
+
${focusRingStyle({ scope: 'options-comment' }, '[part="comment"]')}
|
|
132
174
|
[part="comment"][data-state="commented"] {
|
|
133
|
-
color: var(--crtr-color-accent);
|
|
175
|
+
color: var(--crtr-options-comment-commented-text-color, var(--crtr-color-accent));
|
|
134
176
|
}
|
|
135
177
|
|
|
136
178
|
[part="comment"] svg {
|
|
137
|
-
width: 0.
|
|
138
|
-
height: 0.
|
|
139
|
-
fill: none;
|
|
140
|
-
stroke: currentColor;
|
|
141
|
-
stroke-width: 1.25;
|
|
142
|
-
stroke-linecap: round;
|
|
143
|
-
stroke-linejoin: round;
|
|
179
|
+
width: var(--crtr-options-comment-icon-size, 0.875em);
|
|
180
|
+
height: var(--crtr-options-comment-icon-size, 0.875em);
|
|
181
|
+
fill: var(--crtr-options-comment-icon-fill, none);
|
|
182
|
+
stroke: var(--crtr-options-comment-icon-stroke-color, currentColor);
|
|
183
|
+
stroke-width: var(--crtr-options-comment-icon-stroke-width, 1.25);
|
|
184
|
+
stroke-linecap: var(--crtr-options-comment-icon-stroke-cap, round);
|
|
185
|
+
stroke-linejoin: var(--crtr-options-comment-icon-stroke-join, round);
|
|
144
186
|
}
|
|
145
187
|
|
|
146
188
|
[part="comment-popover"] {
|
|
147
189
|
position: absolute;
|
|
148
|
-
top: calc(100% + var(--crtr-space-1));
|
|
190
|
+
top: calc(100% + var(--crtr-options-comment-popover-offset, var(--crtr-space-1)));
|
|
149
191
|
right: 0;
|
|
150
192
|
z-index: 2;
|
|
151
193
|
display: grid;
|
|
152
|
-
gap: var(--crtr-space-2);
|
|
153
|
-
width: min(
|
|
154
|
-
padding: var(--crtr-space-3);
|
|
155
|
-
background: var(--crtr-color-background);
|
|
156
|
-
border:
|
|
157
|
-
|
|
158
|
-
|
|
194
|
+
gap: var(--crtr-options-comment-popover-gap, var(--crtr-space-2));
|
|
195
|
+
width: var(--crtr-options-comment-popover-width, min(20em, 100%));
|
|
196
|
+
padding: var(--crtr-options-comment-popover-padding, var(--crtr-space-3));
|
|
197
|
+
background: var(--crtr-options-comment-popover-fill, var(--crtr-color-background));
|
|
198
|
+
border: var(--crtr-options-comment-popover-border-width, 1px)
|
|
199
|
+
var(--crtr-options-comment-popover-border-style, solid)
|
|
200
|
+
var(--crtr-options-comment-popover-border-color, var(--crtr-color-border));
|
|
201
|
+
border-radius: var(--crtr-options-comment-popover-radius, var(--crtr-radius-medium));
|
|
202
|
+
box-shadow: var(--crtr-options-comment-popover-shadow, var(--crtr-shadow));
|
|
159
203
|
}
|
|
160
204
|
|
|
161
205
|
[part="comment-popover"][hidden] {
|
|
162
206
|
display: none;
|
|
163
207
|
}
|
|
164
208
|
|
|
209
|
+
/* The popover's own prose, in the page's voice — not a caps-mono field label shouting an
|
|
210
|
+
option id at someone who just clicked that option. */
|
|
165
211
|
[part="comment-subject"] {
|
|
166
|
-
margin: 0;
|
|
167
|
-
color: var(--crtr-color-text-muted);
|
|
168
|
-
font-
|
|
169
|
-
font-size: 0.75em;
|
|
170
|
-
letter-spacing: 0.06em;
|
|
171
|
-
text-transform: uppercase;
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
[part="comment-input"],
|
|
175
|
-
[part="freetext-input"] {
|
|
176
|
-
width: 100%;
|
|
177
|
-
box-sizing: border-box;
|
|
178
|
-
padding: var(--crtr-space-2);
|
|
179
|
-
color: var(--crtr-color-text);
|
|
180
|
-
font: inherit;
|
|
181
|
-
background: var(--crtr-color-surface);
|
|
182
|
-
border: 1px solid var(--crtr-color-border);
|
|
183
|
-
border-radius: var(--crtr-radius-small);
|
|
184
|
-
resize: vertical;
|
|
212
|
+
margin: var(--crtr-options-comment-subject-margin, 0);
|
|
213
|
+
color: var(--crtr-options-comment-subject-text-color, var(--crtr-color-text-muted));
|
|
214
|
+
font-size: var(--crtr-options-comment-subject-font-size, 0.875em);
|
|
185
215
|
}
|
|
186
216
|
|
|
217
|
+
/* Two calls rather than one selector list: a comment box and the freetext box are two things
|
|
218
|
+
a person can point at, so each takes its own scope. They still share the family layer
|
|
219
|
+
underneath, so they cannot drift apart unless a host parts them on purpose. */
|
|
220
|
+
${writingBoxStyle({ scope: 'options-comment-input' }, '[part="comment-input"]')}
|
|
221
|
+
${writingBoxStyle({ scope: 'options-freetext-input' }, '[part="freetext-input"]')}
|
|
187
222
|
[part="comment-input"] {
|
|
188
|
-
min-height: 4.
|
|
223
|
+
min-height: var(--crtr-options-comment-input-min-height, 4.5em);
|
|
224
|
+
resize: vertical;
|
|
189
225
|
}
|
|
190
226
|
|
|
191
227
|
[part="freetext-input"] {
|
|
192
|
-
min-height:
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
[part="comment-input"]:focus-visible,
|
|
196
|
-
[part="freetext-input"]:focus-visible {
|
|
197
|
-
outline: 2px solid var(--crtr-color-focus);
|
|
198
|
-
outline-offset: -1px;
|
|
228
|
+
min-height: var(--crtr-options-freetext-input-min-height, 4em);
|
|
229
|
+
resize: vertical;
|
|
199
230
|
}
|
|
200
231
|
|
|
201
232
|
[part="comment-actions"] {
|
|
202
233
|
display: flex;
|
|
203
234
|
align-items: center;
|
|
204
235
|
justify-content: space-between;
|
|
205
|
-
gap: var(--crtr-space-2);
|
|
236
|
+
gap: var(--crtr-options-comment-actions-gap, var(--crtr-space-2));
|
|
206
237
|
}
|
|
207
238
|
|
|
208
239
|
[part="comment-hint"] {
|
|
209
|
-
color: var(--crtr-color-text-muted);
|
|
210
|
-
font-
|
|
211
|
-
font-size: 0.75em;
|
|
212
|
-
letter-spacing: 0.06em;
|
|
213
|
-
text-transform: uppercase;
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
[part="comment-cancel"],
|
|
217
|
-
[part="comment-save"] {
|
|
218
|
-
padding: var(--crtr-space-1) var(--crtr-space-2);
|
|
219
|
-
font: inherit;
|
|
220
|
-
font-size: 0.9em;
|
|
221
|
-
border-radius: var(--crtr-radius-small);
|
|
222
|
-
cursor: pointer;
|
|
240
|
+
color: var(--crtr-options-comment-hint-text-color, var(--crtr-color-text-muted));
|
|
241
|
+
font-size: var(--crtr-options-comment-hint-font-size, 0.875em);
|
|
223
242
|
}
|
|
224
243
|
|
|
244
|
+
/* The two buttons are one shape drawn twice rather than one rule serving two parts, because
|
|
245
|
+
a scope that named both would make "the cancel button's fill" unsayable. */
|
|
225
246
|
[part="comment-cancel"] {
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
247
|
+
padding: var(--crtr-options-comment-cancel-padding, var(--crtr-space-1) var(--crtr-space-2));
|
|
248
|
+
font: var(--crtr-options-comment-cancel-font, inherit);
|
|
249
|
+
font-size: var(--crtr-options-comment-cancel-font-size, 0.875em);
|
|
250
|
+
color: var(--crtr-options-comment-cancel-text-color, var(--crtr-color-text-muted));
|
|
251
|
+
background: var(--crtr-options-comment-cancel-fill, transparent);
|
|
252
|
+
border: var(--crtr-options-comment-cancel-border-width, 1px)
|
|
253
|
+
var(--crtr-options-comment-cancel-border-style, solid)
|
|
254
|
+
var(--crtr-options-comment-cancel-border-color, transparent);
|
|
255
|
+
border-radius: var(--crtr-options-comment-cancel-radius, var(--crtr-radius-small));
|
|
256
|
+
cursor: pointer;
|
|
257
|
+
transition: color var(--crtr-options-comment-cancel-transition, var(--crtr-control-transition, 120ms ease)),
|
|
258
|
+
background-color var(--crtr-options-comment-cancel-transition, var(--crtr-control-transition, 120ms ease)),
|
|
259
|
+
border-color var(--crtr-options-comment-cancel-transition, var(--crtr-control-transition, 120ms ease));
|
|
229
260
|
}
|
|
230
261
|
|
|
231
262
|
[part="comment-cancel"]:hover {
|
|
232
|
-
color: var(--crtr-color-text);
|
|
233
|
-
background: var(--crtr-color-surface-hover);
|
|
263
|
+
color: var(--crtr-options-comment-cancel-hover-text-color, var(--crtr-color-text));
|
|
264
|
+
background: var(--crtr-options-comment-cancel-hover-fill, var(--crtr-color-surface-hover));
|
|
234
265
|
}
|
|
235
266
|
|
|
236
267
|
[part="comment-save"] {
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
268
|
+
padding: var(--crtr-options-comment-save-padding, var(--crtr-space-1) var(--crtr-space-2));
|
|
269
|
+
font: var(--crtr-options-comment-save-font, inherit);
|
|
270
|
+
font-size: var(--crtr-options-comment-save-font-size, 0.875em);
|
|
271
|
+
color: var(--crtr-options-comment-save-text-color, var(--crtr-color-accent-contrast));
|
|
272
|
+
background: var(--crtr-options-comment-save-fill, var(--crtr-color-accent));
|
|
273
|
+
border: var(--crtr-options-comment-save-border-width, 1px)
|
|
274
|
+
var(--crtr-options-comment-save-border-style, solid)
|
|
275
|
+
var(--crtr-options-comment-save-border-color, var(--crtr-color-accent));
|
|
276
|
+
border-radius: var(--crtr-options-comment-save-radius, var(--crtr-radius-small));
|
|
277
|
+
cursor: pointer;
|
|
278
|
+
transition: color var(--crtr-options-comment-save-transition, var(--crtr-control-transition, 120ms ease)),
|
|
279
|
+
background-color var(--crtr-options-comment-save-transition, var(--crtr-control-transition, 120ms ease)),
|
|
280
|
+
border-color var(--crtr-options-comment-save-transition, var(--crtr-control-transition, 120ms ease));
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/* Mixing the accent toward the text colour darkens on a light token set and lightens on a
|
|
284
|
+
dark one — a brightness filter moves the wrong way on one of them. */
|
|
285
|
+
[part="comment-save"]:hover {
|
|
286
|
+
background: var(
|
|
287
|
+
--crtr-options-comment-save-hover-fill,
|
|
288
|
+
color-mix(in srgb, var(--crtr-color-accent) 88%, var(--crtr-color-text))
|
|
289
|
+
);
|
|
290
|
+
border-color: var(
|
|
291
|
+
--crtr-options-comment-save-hover-border-color,
|
|
292
|
+
color-mix(in srgb, var(--crtr-color-accent) 88%, var(--crtr-color-text))
|
|
293
|
+
);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
${focusRingStyle({ scope: 'options-comment-cancel' }, '[part="comment-cancel"]')}
|
|
297
|
+
${focusRingStyle({ scope: 'options-comment-save' }, '[part="comment-save"]')}
|
|
248
298
|
[part="freetext"] {
|
|
249
299
|
display: grid;
|
|
250
|
-
gap: var(--crtr-space-1);
|
|
300
|
+
gap: var(--crtr-options-freetext-gap, var(--crtr-space-1));
|
|
251
301
|
}
|
|
252
302
|
|
|
253
303
|
[part="freetext-label"] {
|
|
254
|
-
color: var(--crtr-color-text-muted);
|
|
255
|
-
font-size: 0.
|
|
304
|
+
color: var(--crtr-options-freetext-label-text-color, var(--crtr-color-text-muted));
|
|
305
|
+
font-size: var(--crtr-options-freetext-label-font-size, 0.875em);
|
|
256
306
|
}
|
|
257
307
|
|
|
258
308
|
[part="unavailable"] {
|
|
259
|
-
padding: var(--crtr-space-3);
|
|
260
|
-
color: var(--crtr-color-text-muted);
|
|
261
|
-
background: var(--crtr-color-surface);
|
|
262
|
-
border:
|
|
263
|
-
|
|
309
|
+
padding: var(--crtr-options-unavailable-padding, var(--crtr-space-3));
|
|
310
|
+
color: var(--crtr-options-unavailable-text-color, var(--crtr-color-text-muted));
|
|
311
|
+
background: var(--crtr-options-unavailable-fill, var(--crtr-color-surface));
|
|
312
|
+
border: var(--crtr-options-unavailable-border-width, 1px)
|
|
313
|
+
var(--crtr-options-unavailable-border-style, solid)
|
|
314
|
+
var(--crtr-options-unavailable-border-color, var(--crtr-color-border));
|
|
315
|
+
border-radius: var(--crtr-options-unavailable-radius, var(--crtr-radius-medium));
|
|
264
316
|
}
|
|
265
317
|
|
|
266
318
|
[part="reason"] {
|
|
267
|
-
color: var(--crtr-color-text);
|
|
268
|
-
font-family: var(--crtr-font-mono);
|
|
319
|
+
color: var(--crtr-options-reason-text-color, var(--crtr-color-text));
|
|
320
|
+
font-family: var(--crtr-options-reason-font-family, var(--crtr-font-mono));
|
|
269
321
|
}
|
|
270
322
|
`;
|
|
323
|
+
/** Ids are scoped to this element's shadow root, so one constant is unique everywhere. */
|
|
324
|
+
const LABEL_ID = 'prompt-label';
|
|
271
325
|
let groupCounter = 0;
|
|
272
326
|
/**
|
|
273
327
|
* Whether a key belongs to something being written in rather than to the list grammar.
|
|
@@ -284,6 +338,8 @@ function isTextEntry(target) {
|
|
|
284
338
|
return target.type !== 'checkbox' && target.type !== 'radio';
|
|
285
339
|
}
|
|
286
340
|
class CrtrOptionsElement extends HTMLElement {
|
|
341
|
+
/** The host marks a resolved or canceled page `inert`; that is this element's read-only signal. */
|
|
342
|
+
static observedAttributes = ['inert'];
|
|
287
343
|
#shadow;
|
|
288
344
|
#config;
|
|
289
345
|
#slotId;
|
|
@@ -317,12 +373,31 @@ class CrtrOptionsElement extends HTMLElement {
|
|
|
317
373
|
this.#config = config;
|
|
318
374
|
this.#order = config.options.map((option) => option.id);
|
|
319
375
|
this.#hydrate(config);
|
|
376
|
+
// Registered whether or not the person ever selects anything: the pager reports every
|
|
377
|
+
// response-bearing slot at submit, and an empty selection is an answer the host validates
|
|
378
|
+
// and names, where an absent slot is only a map the host cannot read.
|
|
379
|
+
if (this.#slotId !== undefined)
|
|
380
|
+
registerSlotResponse(this, this.#slotId, () => this.#response());
|
|
320
381
|
this.#render(config);
|
|
321
382
|
this.#sync();
|
|
322
383
|
}
|
|
323
384
|
disconnectedCallback() {
|
|
324
385
|
document.removeEventListener('pointerdown', this.#onDocumentPointerDown, true);
|
|
325
386
|
}
|
|
387
|
+
attributeChangedCallback() {
|
|
388
|
+
if (this.isConnected)
|
|
389
|
+
this.#syncReadOnly();
|
|
390
|
+
}
|
|
391
|
+
/** Read-only is repainted after every build, because a rebuild replaces the controls. */
|
|
392
|
+
#syncReadOnly() {
|
|
393
|
+
const readOnly = isReadOnly(this);
|
|
394
|
+
applyReadOnly(this.#shadow, readOnly);
|
|
395
|
+
// A page can go read-only while a box is open — the host sets `inert` on a ticket that
|
|
396
|
+
// resolves under it. A box that can no longer save is closed rather than left there
|
|
397
|
+
// taking words nothing will keep.
|
|
398
|
+
if (readOnly && this.#commentingId !== undefined)
|
|
399
|
+
this.#closeComment(false);
|
|
400
|
+
}
|
|
326
401
|
/**
|
|
327
402
|
* Seed local state from the answer the host is already holding, so a reopened page shows
|
|
328
403
|
* what it would submit. Ids that no longer name an option are dropped, and single-select
|
|
@@ -362,10 +437,24 @@ class CrtrOptionsElement extends HTMLElement {
|
|
|
362
437
|
style.textContent = STYLE;
|
|
363
438
|
const container = document.createElement('div');
|
|
364
439
|
container.part.add('container');
|
|
440
|
+
const labelled = config.label !== undefined && config.label !== '';
|
|
441
|
+
if (labelled) {
|
|
442
|
+
const label = document.createElement('p');
|
|
443
|
+
label.part.add('label');
|
|
444
|
+
label.id = LABEL_ID;
|
|
445
|
+
label.textContent = config.label ?? '';
|
|
446
|
+
container.append(label);
|
|
447
|
+
}
|
|
365
448
|
const list = document.createElement('div');
|
|
366
449
|
list.part.add('list');
|
|
450
|
+
// A labelled multi-select is a named group of checkboxes; single-select is already one
|
|
451
|
+
// control, so the radiogroup role it always carries takes the name instead.
|
|
367
452
|
if (config.mode === 'single')
|
|
368
453
|
list.setAttribute('role', 'radiogroup');
|
|
454
|
+
else if (labelled)
|
|
455
|
+
list.setAttribute('role', 'group');
|
|
456
|
+
if (labelled)
|
|
457
|
+
list.setAttribute('aria-labelledby', LABEL_ID);
|
|
369
458
|
groupCounter += 1;
|
|
370
459
|
const group = `crtr-options-${groupCounter}`;
|
|
371
460
|
for (const option of config.options)
|
|
@@ -404,6 +493,24 @@ class CrtrOptionsElement extends HTMLElement {
|
|
|
404
493
|
text.append(description);
|
|
405
494
|
}
|
|
406
495
|
label.append(control, text);
|
|
496
|
+
// No comment affordance where a comment has nowhere to go — see `canRecordComments`.
|
|
497
|
+
const parts = canRecordComments(this.#slotId) ? this.#buildComment(option) : undefined;
|
|
498
|
+
root.append(label);
|
|
499
|
+
if (parts !== undefined)
|
|
500
|
+
root.append(parts.trigger, parts.popover);
|
|
501
|
+
root.addEventListener('mouseenter', () => this.#setActive(option.id));
|
|
502
|
+
root.addEventListener('mouseleave', () => this.#clearActive(option.id));
|
|
503
|
+
this.#rows.set(option.id, {
|
|
504
|
+
id: option.id,
|
|
505
|
+
label: option.label,
|
|
506
|
+
root,
|
|
507
|
+
control,
|
|
508
|
+
...(parts === undefined ? {} : { comment: parts }),
|
|
509
|
+
});
|
|
510
|
+
return root;
|
|
511
|
+
}
|
|
512
|
+
/** The trigger and its box, built together because they are only ever used together. */
|
|
513
|
+
#buildComment(option) {
|
|
407
514
|
const comment = document.createElement('button');
|
|
408
515
|
comment.part.add('comment');
|
|
409
516
|
comment.type = 'button';
|
|
@@ -454,11 +561,7 @@ class CrtrOptionsElement extends HTMLElement {
|
|
|
454
561
|
buttons.append(cancel, save);
|
|
455
562
|
actions.append(hint, buttons);
|
|
456
563
|
popover.append(subject, input, actions);
|
|
457
|
-
|
|
458
|
-
root.addEventListener('mouseenter', () => this.#setActive(option.id));
|
|
459
|
-
root.addEventListener('mouseleave', () => this.#clearActive(option.id));
|
|
460
|
-
this.#rows.set(option.id, { id: option.id, label: option.label, root, control, comment, popover, input, hint });
|
|
461
|
-
return root;
|
|
564
|
+
return { trigger: comment, popover, input, hint };
|
|
462
565
|
}
|
|
463
566
|
#renderFreetext(config) {
|
|
464
567
|
const wrapper = document.createElement('label');
|
|
@@ -522,7 +625,7 @@ class CrtrOptionsElement extends HTMLElement {
|
|
|
522
625
|
this.#sync();
|
|
523
626
|
}
|
|
524
627
|
#toggle(optionId, selected) {
|
|
525
|
-
if (this.#config === undefined)
|
|
628
|
+
if (this.#config === undefined || isReadOnly(this))
|
|
526
629
|
return;
|
|
527
630
|
if (this.#config.mode === 'single') {
|
|
528
631
|
this.#selected = [optionId];
|
|
@@ -548,19 +651,21 @@ class CrtrOptionsElement extends HTMLElement {
|
|
|
548
651
|
#onDocumentPointerDown = (event) => {
|
|
549
652
|
if (this.#commentingId === undefined)
|
|
550
653
|
return;
|
|
551
|
-
const
|
|
552
|
-
if (
|
|
654
|
+
const parts = this.#rows.get(this.#commentingId)?.comment;
|
|
655
|
+
if (parts === undefined)
|
|
553
656
|
return;
|
|
554
657
|
const path = event.composedPath();
|
|
555
|
-
if (path.includes(
|
|
658
|
+
if (path.includes(parts.popover) || path.includes(parts.trigger))
|
|
556
659
|
return;
|
|
557
660
|
// No focus restore: the press is on its way somewhere else, and pulling focus back to
|
|
558
661
|
// the trigger would fight it.
|
|
559
662
|
this.#closeComment(false);
|
|
560
663
|
};
|
|
561
664
|
#openComment(optionId) {
|
|
562
|
-
const
|
|
563
|
-
|
|
665
|
+
const parts = this.#rows.get(optionId)?.comment;
|
|
666
|
+
// A box opens only where its comment can land: never on a read-only page, whose textareas
|
|
667
|
+
// are already `readOnly`, so it would take focus and refuse every keystroke.
|
|
668
|
+
if (parts === undefined || isReadOnly(this))
|
|
564
669
|
return;
|
|
565
670
|
if (this.#commentingId === undefined) {
|
|
566
671
|
document.addEventListener('pointerdown', this.#onDocumentPointerDown, true);
|
|
@@ -568,27 +673,27 @@ class CrtrOptionsElement extends HTMLElement {
|
|
|
568
673
|
this.#commentingId = optionId;
|
|
569
674
|
this.#activeId = optionId;
|
|
570
675
|
// Seeded from the saved comment every time it opens.
|
|
571
|
-
|
|
676
|
+
parts.input.value = findComment(this.#comments, anchorFor(optionId))?.text ?? '';
|
|
572
677
|
this.#sync();
|
|
573
|
-
|
|
678
|
+
parts.input.focus();
|
|
574
679
|
}
|
|
575
680
|
#closeComment(restoreFocus = true) {
|
|
576
681
|
if (this.#commentingId === undefined)
|
|
577
682
|
return;
|
|
578
|
-
const
|
|
683
|
+
const parts = this.#rows.get(this.#commentingId)?.comment;
|
|
579
684
|
this.#commentingId = undefined;
|
|
580
685
|
document.removeEventListener('pointerdown', this.#onDocumentPointerDown, true);
|
|
581
686
|
this.#sync();
|
|
582
687
|
if (restoreFocus)
|
|
583
|
-
|
|
688
|
+
parts?.trigger.focus();
|
|
584
689
|
}
|
|
585
690
|
#commitComment(optionId) {
|
|
586
|
-
const
|
|
587
|
-
if (
|
|
691
|
+
const parts = this.#rows.get(optionId)?.comment;
|
|
692
|
+
if (parts === undefined || isReadOnly(this))
|
|
588
693
|
return;
|
|
589
694
|
// Clearing the box deletes the comment — `setComment` treats empty text as removal, so
|
|
590
695
|
// there is no separate destructive control.
|
|
591
|
-
this.#comments = setComment(this.#comments, anchorFor(optionId),
|
|
696
|
+
this.#comments = setComment(this.#comments, anchorFor(optionId), parts.input.value);
|
|
592
697
|
this.#closeComment();
|
|
593
698
|
this.#emit();
|
|
594
699
|
}
|
|
@@ -599,20 +704,22 @@ class CrtrOptionsElement extends HTMLElement {
|
|
|
599
704
|
row.control.checked = selected;
|
|
600
705
|
row.root.dataset.selected = String(selected);
|
|
601
706
|
row.root.dataset.active = String(this.#activeId === row.id);
|
|
707
|
+
const parts = row.comment;
|
|
708
|
+
if (parts === undefined)
|
|
709
|
+
continue;
|
|
602
710
|
const comment = findComment(this.#comments, anchorFor(row.id));
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
711
|
+
parts.trigger.dataset.state = comment === undefined ? 'empty' : 'commented';
|
|
712
|
+
parts.trigger.innerHTML = comment === undefined ? BUBBLE_PLUS : BUBBLE_TEXT;
|
|
713
|
+
parts.trigger.setAttribute('aria-label', comment === undefined ? `Comment on ${row.label}` : `Edit comment on ${row.label}`);
|
|
606
714
|
const open = this.#commentingId === row.id;
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
715
|
+
parts.trigger.setAttribute('aria-expanded', String(open));
|
|
716
|
+
parts.popover.hidden = !open;
|
|
717
|
+
parts.hint.textContent = comment === undefined ? '\u2318\u21b5 to save' : 'empty to delete';
|
|
610
718
|
}
|
|
719
|
+
this.#syncReadOnly();
|
|
611
720
|
}
|
|
612
|
-
/** The complete current response
|
|
613
|
-
#
|
|
614
|
-
if (this.#slotId === undefined)
|
|
615
|
-
return;
|
|
721
|
+
/** The complete current response — what every change hands `respond()`. */
|
|
722
|
+
#response() {
|
|
616
723
|
const response = {
|
|
617
724
|
selectedOptionIds: [...this.#selected],
|
|
618
725
|
comments: this.#comments.map((comment) => ({ ...comment })),
|
|
@@ -620,7 +727,13 @@ class CrtrOptionsElement extends HTMLElement {
|
|
|
620
727
|
if (this.#config?.allowFreetext === true && this.#freetext !== undefined) {
|
|
621
728
|
response.freetext = this.#freetext;
|
|
622
729
|
}
|
|
623
|
-
|
|
730
|
+
return response;
|
|
731
|
+
}
|
|
732
|
+
/** The complete current response, on every user change — never a delta. */
|
|
733
|
+
#emit() {
|
|
734
|
+
if (this.#slotId === undefined)
|
|
735
|
+
return;
|
|
736
|
+
respond(this.#slotId, this.#response());
|
|
624
737
|
}
|
|
625
738
|
}
|
|
626
739
|
function anchorFor(optionId) {
|
|
@@ -20,5 +20,11 @@
|
|
|
20
20
|
*
|
|
21
21
|
* A single-step page renders no pager chrome at all — no progress, no forward/back — just
|
|
22
22
|
* the submit affordance, because there is nothing to flip.
|
|
23
|
+
*
|
|
24
|
+
* Both elements paint entirely through `--crtr-page-*` component tokens — the same element
|
|
25
|
+
* scope `bundle.css` gives `crtr-pages` and `crtr-page`, because a person looking at the page
|
|
26
|
+
* sees one thing. The pager's buttons are the bundle's ordinary buttons, so their focus ring
|
|
27
|
+
* and their motion chain through the shared `--crtr-control-*` family before falling back to
|
|
28
|
+
* the kit default.
|
|
23
29
|
*/
|
|
24
30
|
export {};
|