@zylem/ui 0.8.13 → 0.9.0
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/styles.css +1 -1
- package/package.json +1 -1
- package/src/components/Accordion/Accordion.tsx +4 -1
- package/src/components/BuildLabel/BuildLabel.tsx +0 -1
- package/src/components/BuildLabel/mount-build-label.ts +0 -1
- package/src/components/Callout/Callout.tsx +1 -1
- package/src/components/Card/Card.css.ts +1 -0
- package/src/components/Card/Card.tsx +11 -2
- package/src/components/Checkbox/Checkbox.tsx +4 -2
- package/src/components/ColorPalette/ColorPalette.tsx +1 -1
- package/src/components/Console/Console.tsx +3 -0
- package/src/components/Dialog/Dialog.css.ts +41 -17
- package/src/components/Dialog/Dialog.tsx +134 -53
- package/src/components/FrameStrip/FrameStrip.tsx +2 -3
- package/src/components/ItemPicker/ItemPicker.tsx +46 -33
- package/src/components/MenuButton/MenuButton.tsx +1 -0
- package/src/components/Menubar/Menubar.css.ts +29 -29
- package/src/components/Mixer/Knob.tsx +10 -3
- package/src/components/Mixer/Mixer.css.ts +19 -0
- package/src/components/Mixer/MixerStrip.tsx +29 -8
- package/src/components/NumberField/NumberField.tsx +5 -2
- package/src/components/Panel/Panel.css.ts +1 -0
- package/src/components/Panel/Panel.tsx +9 -1
- package/src/components/PianoRoll/PianoRoll.tsx +1 -0
- package/src/components/Progress/Progress.tsx +3 -0
- package/src/components/Property/Property.css.ts +13 -0
- package/src/components/Property/Property.tsx +3 -2
- package/src/components/SearchInput/SearchInput.tsx +1 -1
- package/src/components/Select/Select.tsx +3 -1
- package/src/components/Sidebar/Sidebar.css.ts +1 -0
- package/src/components/Sidebar/Sidebar.tsx +23 -5
- package/src/components/Slider/Slider.tsx +4 -2
- package/src/components/Switch/Switch.tsx +3 -1
- package/src/components/Tabs/Tabs.css.ts +0 -5
- package/src/components/Tabs/Tabs.tsx +23 -12
- package/src/components/TextField/TextField.tsx +4 -0
- package/src/components/TimelineLanes/TimelineLanes.css.ts +6 -0
- package/src/components/TimelineLanes/TimelineLanes.tsx +52 -4
- package/src/components/TimelineLanes/lanes-math.ts +51 -0
- package/src/components/TimelineTrack/TimelineTrack.tsx +16 -3
- package/src/components/TitleBar/TitleBar.css.ts +1 -0
- package/src/components/TitleBar/TitleBar.tsx +7 -1
- package/src/components/Toast/Toast.tsx +1 -2
- package/src/components/ToolButton/ToolButton.tsx +1 -1
- package/src/components/Toolbar/Toolbar.tsx +6 -1
- package/src/components/ToolbarButton/ToolbarButton.tsx +9 -2
- package/src/components/ToolboxButton/ToolboxButton.tsx +2 -1
- package/src/components/TrackHeader/TrackHeader.css.ts +21 -0
- package/src/components/TrackHeader/TrackHeader.tsx +37 -12
- package/src/components/TransportBar/TransportBar.tsx +4 -1
- package/src/components/Waveform/Waveform.css.ts +10 -0
- package/src/components/Waveform/Waveform.tsx +59 -27
- package/src/components/WindowControls/WindowControls.tsx +4 -4
- package/src/components/index.ts +1 -0
- package/src/components/shared/Heading.tsx +30 -0
- package/src/global/accordion.css.ts +6 -1
- package/src/global/common.css.ts +13 -0
- package/src/layers/LayerContext.tsx +2 -2
|
@@ -1,7 +1,15 @@
|
|
|
1
|
-
import { createEffect, createMemo, createSignal, onCleanup, onMount, Show } from 'solid-js';
|
|
2
|
-
import { clamp } from '../TimelineLanes/lanes-math';
|
|
1
|
+
import { createEffect, createMemo, createSignal, For, onCleanup, onMount, Show } from 'solid-js';
|
|
2
|
+
import { clamp, describeSeconds, sliderKeyValue } from '../TimelineLanes/lanes-math';
|
|
3
3
|
import { computePeaks, peaksFromAudioBuffer } from './peaks';
|
|
4
4
|
|
|
5
|
+
/** Seconds the trim edges must stay apart. */
|
|
6
|
+
const MIN_REGION = 0.01;
|
|
7
|
+
|
|
8
|
+
const EDGES = [
|
|
9
|
+
{ edge: 'start', label: 'Trim start' },
|
|
10
|
+
{ edge: 'end', label: 'Trim end' },
|
|
11
|
+
] as const;
|
|
12
|
+
|
|
5
13
|
export type { PeakSource } from './peaks';
|
|
6
14
|
|
|
7
15
|
export interface WaveformRegion {
|
|
@@ -17,7 +25,10 @@ export interface WaveformProps {
|
|
|
17
25
|
buffer?: { duration: number; numberOfChannels: number; getChannelData(channel: number): Float32Array };
|
|
18
26
|
/** Seconds the peaks span; defaults to `buffer.duration`. Needed for regions and the playhead. */
|
|
19
27
|
duration?: number;
|
|
20
|
-
/**
|
|
28
|
+
/**
|
|
29
|
+
* Highlighted span with draggable edges. Focused, an edge also takes the
|
|
30
|
+
* arrow keys (1% of the duration, 10% with Shift) and Home / End.
|
|
31
|
+
*/
|
|
21
32
|
region?: WaveformRegion;
|
|
22
33
|
onRegionChange?: (region: WaveformRegion) => void;
|
|
23
34
|
onRegionCommit?: (region: WaveformRegion) => void;
|
|
@@ -106,14 +117,15 @@ export function Waveform(props: WaveformProps) {
|
|
|
106
117
|
event.preventDefault();
|
|
107
118
|
event.stopPropagation();
|
|
108
119
|
const target = event.currentTarget as HTMLElement;
|
|
120
|
+
target.focus({ preventScroll: true });
|
|
109
121
|
target.setPointerCapture?.(event.pointerId);
|
|
110
122
|
let last = region;
|
|
111
123
|
const move = (moveEvent: PointerEvent) => {
|
|
112
124
|
const seconds = secondsAt(moveEvent.clientX);
|
|
113
125
|
last =
|
|
114
126
|
edge === 'start'
|
|
115
|
-
? { start: Math.min(seconds, region.end -
|
|
116
|
-
: { start: region.start, end: Math.max(seconds, region.start +
|
|
127
|
+
? { start: Math.min(seconds, region.end - MIN_REGION), end: region.end }
|
|
128
|
+
: { start: region.start, end: Math.max(seconds, region.start + MIN_REGION) };
|
|
117
129
|
props.onRegionChange?.(last);
|
|
118
130
|
};
|
|
119
131
|
const stop = () => {
|
|
@@ -128,6 +140,29 @@ export function Waveform(props: WaveformProps) {
|
|
|
128
140
|
target.addEventListener('pointercancel', stop);
|
|
129
141
|
};
|
|
130
142
|
|
|
143
|
+
/** The span each edge may move in, short of crossing the other edge. */
|
|
144
|
+
const edgeBounds = (edge: 'start' | 'end', region: WaveformRegion) =>
|
|
145
|
+
edge === 'start'
|
|
146
|
+
? { min: 0, max: Math.max(0, region.end - MIN_REGION) }
|
|
147
|
+
: { min: Math.min(duration(), region.start + MIN_REGION), max: duration() };
|
|
148
|
+
|
|
149
|
+
const nudgeEdge = (edge: 'start' | 'end') => (event: KeyboardEvent) => {
|
|
150
|
+
const region = props.region;
|
|
151
|
+
if (props.disabled || !region) return;
|
|
152
|
+
const next = sliderKeyValue(event, {
|
|
153
|
+
value: region[edge],
|
|
154
|
+
...edgeBounds(edge, region),
|
|
155
|
+
step: duration() / 100,
|
|
156
|
+
});
|
|
157
|
+
if (next === null) return;
|
|
158
|
+
event.preventDefault();
|
|
159
|
+
// Page-level shortcuts (Home to rewind, arrows to seek) must not also act on it.
|
|
160
|
+
event.stopPropagation();
|
|
161
|
+
const changed = { ...region, [edge]: next };
|
|
162
|
+
props.onRegionChange?.(changed);
|
|
163
|
+
props.onRegionCommit?.(changed);
|
|
164
|
+
};
|
|
165
|
+
|
|
131
166
|
return (
|
|
132
167
|
<div
|
|
133
168
|
ref={host}
|
|
@@ -153,28 +188,25 @@ export function Waveform(props: WaveformProps) {
|
|
|
153
188
|
class="zylem-waveform-shade"
|
|
154
189
|
style={{ left: `${percent(region().end)}%`, right: 0 }}
|
|
155
190
|
/>
|
|
156
|
-
<
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
aria-valuemin={0}
|
|
176
|
-
aria-valuemax={duration()}
|
|
177
|
-
/>
|
|
191
|
+
<For each={EDGES}>
|
|
192
|
+
{(edge) => (
|
|
193
|
+
<div
|
|
194
|
+
class="zylem-waveform-edge"
|
|
195
|
+
data-edge={edge.edge}
|
|
196
|
+
style={{ left: `${percent(region()[edge.edge])}%` }}
|
|
197
|
+
onPointerDown={beginRegionDrag(edge.edge)}
|
|
198
|
+
onKeyDown={nudgeEdge(edge.edge)}
|
|
199
|
+
role="slider"
|
|
200
|
+
tabIndex={props.disabled ? -1 : 0}
|
|
201
|
+
aria-label={edge.label}
|
|
202
|
+
aria-valuenow={region()[edge.edge]}
|
|
203
|
+
aria-valuemin={edgeBounds(edge.edge, region()).min}
|
|
204
|
+
aria-valuemax={edgeBounds(edge.edge, region()).max}
|
|
205
|
+
aria-valuetext={describeSeconds(region()[edge.edge])}
|
|
206
|
+
aria-disabled={props.disabled ? 'true' : undefined}
|
|
207
|
+
/>
|
|
208
|
+
)}
|
|
209
|
+
</For>
|
|
178
210
|
</>
|
|
179
211
|
);
|
|
180
212
|
}}
|
|
@@ -23,7 +23,7 @@ export interface WindowControlsProps {
|
|
|
23
23
|
export function WindowControls(props: WindowControlsProps) {
|
|
24
24
|
return (
|
|
25
25
|
<Show when={props.onClose || props.onCollapse}>
|
|
26
|
-
<div class="zylem-window-controls" aria-label="Window controls">
|
|
26
|
+
<div class="zylem-window-controls" role="group" aria-label="Window controls">
|
|
27
27
|
<Show when={props.onCollapse}>
|
|
28
28
|
<button
|
|
29
29
|
type="button"
|
|
@@ -33,9 +33,9 @@ export function WindowControls(props: WindowControlsProps) {
|
|
|
33
33
|
onClick={() => props.onCollapse?.()}
|
|
34
34
|
>
|
|
35
35
|
{props.collapsed ? (
|
|
36
|
-
<ChevronUp size={11} strokeWidth={2.5} />
|
|
36
|
+
<ChevronUp size={11} strokeWidth={2.5} aria-hidden="true" />
|
|
37
37
|
) : (
|
|
38
|
-
<ChevronDown size={11} strokeWidth={2.5} />
|
|
38
|
+
<ChevronDown size={11} strokeWidth={2.5} aria-hidden="true" />
|
|
39
39
|
)}
|
|
40
40
|
</button>
|
|
41
41
|
</Show>
|
|
@@ -48,7 +48,7 @@ export function WindowControls(props: WindowControlsProps) {
|
|
|
48
48
|
data-testid={props.closeTestId}
|
|
49
49
|
onClick={() => props.onClose?.()}
|
|
50
50
|
>
|
|
51
|
-
<X size={11} strokeWidth={2.5} />
|
|
51
|
+
<X size={11} strokeWidth={2.5} aria-hidden="true" />
|
|
52
52
|
</button>
|
|
53
53
|
</Show>
|
|
54
54
|
</div>
|
package/src/components/index.ts
CHANGED
|
@@ -24,6 +24,7 @@ export {
|
|
|
24
24
|
} from '../layers';
|
|
25
25
|
|
|
26
26
|
// Primitives
|
|
27
|
+
export { type HeadingLevel } from './shared/Heading';
|
|
27
28
|
export { Button, type ButtonProps, type ButtonVariant, type ButtonSize } from './Button/Button';
|
|
28
29
|
export { ToolbarButton, type ToolbarButtonProps } from './ToolbarButton/ToolbarButton';
|
|
29
30
|
export { TextField, type TextFieldProps } from './TextField/TextField';
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { JSX } from 'solid-js';
|
|
2
|
+
import { Dynamic } from 'solid-js/web';
|
|
3
|
+
|
|
4
|
+
/** Outline level of a component's title: `2` renders an `<h2>`. */
|
|
5
|
+
export type HeadingLevel = 1 | 2 | 3 | 4 | 5 | 6;
|
|
6
|
+
|
|
7
|
+
/** The element name for a level, for primitives that take an `as` tag. */
|
|
8
|
+
export function headingTag(level: HeadingLevel) {
|
|
9
|
+
return `h${level}` as const;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface HeadingProps {
|
|
13
|
+
level: HeadingLevel;
|
|
14
|
+
id?: string | undefined;
|
|
15
|
+
class?: string | undefined;
|
|
16
|
+
title?: string | undefined;
|
|
17
|
+
children: JSX.Element;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* An `<h1>`…`<h6>` chosen at runtime, for titles whose place in the page
|
|
22
|
+
* outline is the host's call.
|
|
23
|
+
*/
|
|
24
|
+
export function Heading(props: HeadingProps) {
|
|
25
|
+
return (
|
|
26
|
+
<Dynamic component={headingTag(props.level)} id={props.id} class={props.class} title={props.title}>
|
|
27
|
+
{props.children}
|
|
28
|
+
</Dynamic>
|
|
29
|
+
);
|
|
30
|
+
}
|
|
@@ -14,11 +14,14 @@ globalStyle('.zylem-accordion', {
|
|
|
14
14
|
minHeight: 0,
|
|
15
15
|
});
|
|
16
16
|
|
|
17
|
+
// The header is a heading element; its UA margins would open gaps around the
|
|
18
|
+
// trigger, and differ by heading level.
|
|
17
19
|
globalStyle('.accordion-header', {
|
|
18
20
|
position: 'sticky',
|
|
19
21
|
top: 0,
|
|
20
22
|
zIndex: 1,
|
|
21
23
|
display: 'flex',
|
|
24
|
+
margin: 0,
|
|
22
25
|
});
|
|
23
26
|
|
|
24
27
|
globalStyle('.accordion-item', {
|
|
@@ -105,7 +108,9 @@ globalStyle('.zylem-accordion--stack .accordion-content', {
|
|
|
105
108
|
});
|
|
106
109
|
|
|
107
110
|
globalStyle('.accordion-trigger::after', {
|
|
108
|
-
|
|
111
|
+
// The empty alternative text keeps the chevron out of the trigger's
|
|
112
|
+
// accessible name; the first value is for browsers without that syntax.
|
|
113
|
+
content: ["'\\25BC'", "'\\25BC' / ''"],
|
|
109
114
|
position: 'relative',
|
|
110
115
|
color: vars.colors.textSecondary,
|
|
111
116
|
fontSize: '10px',
|
package/src/global/common.css.ts
CHANGED
|
@@ -13,6 +13,19 @@ globalStyle('.overlay', {
|
|
|
13
13
|
pointerEvents: 'none',
|
|
14
14
|
});
|
|
15
15
|
|
|
16
|
+
// Text for assistive tech only: off screen, still in the accessibility tree.
|
|
17
|
+
globalStyle('.zylem-visually-hidden', {
|
|
18
|
+
position: 'absolute',
|
|
19
|
+
width: '1px',
|
|
20
|
+
height: '1px',
|
|
21
|
+
padding: 0,
|
|
22
|
+
margin: '-1px',
|
|
23
|
+
overflow: 'hidden',
|
|
24
|
+
clip: 'rect(0, 0, 0, 0)',
|
|
25
|
+
whiteSpace: 'nowrap',
|
|
26
|
+
border: 0,
|
|
27
|
+
});
|
|
28
|
+
|
|
16
29
|
// Default scrollbars: slim glass rail (use `.zylem-scroll` for the full
|
|
17
30
|
// 13px HyperGlass treatment defined in hyperglass-base.css.ts).
|
|
18
31
|
globalStyle('::-webkit-scrollbar', {
|
|
@@ -60,8 +60,8 @@ export interface LayerScopeProps {
|
|
|
60
60
|
* them can paint above anything outside; and the scope owns the portal container
|
|
61
61
|
* its descendants mount into, so overlays opened inside it stay inside it.
|
|
62
62
|
*
|
|
63
|
-
* A dialog
|
|
64
|
-
*
|
|
63
|
+
* A dialog puts its card in one, so a menu opened within the dialog mounts
|
|
64
|
+
* inside the `<dialog>`, floats over the card, and is not clipped by it.
|
|
65
65
|
*/
|
|
66
66
|
export function LayerScope(props: LayerScopeProps) {
|
|
67
67
|
const [container, setContainer] = createSignal<HTMLElement>();
|