@patchbayhq/svelte 0.1.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/README.md +20 -0
- package/package.json +57 -0
- package/src/Arrows.svelte +22 -0
- package/src/Button.svelte +52 -0
- package/src/ClipStepLauncher.svelte +105 -0
- package/src/ColorSwatches.svelte +23 -0
- package/src/Dial.svelte +68 -0
- package/src/Drop.svelte +25 -0
- package/src/Envelope.svelte +124 -0
- package/src/Gain.svelte +53 -0
- package/src/Grid.svelte +56 -0
- package/src/Label.svelte +13 -0
- package/src/Line.svelte +18 -0
- package/src/MacroRack.svelte +48 -0
- package/src/Menu.svelte +139 -0
- package/src/Meter.svelte +21 -0
- package/src/NumberBox.svelte +34 -0
- package/src/Panel.svelte +13 -0
- package/src/PanelHeader.svelte +28 -0
- package/src/PanelSection.svelte +23 -0
- package/src/Scope.svelte +50 -0
- package/src/Slider.svelte +55 -0
- package/src/StatusIndicator.svelte +26 -0
- package/src/StepSequencer.svelte +77 -0
- package/src/Tabs.svelte +33 -0
- package/src/TextButton.svelte +54 -0
- package/src/Toggle.svelte +32 -0
- package/src/composites.ts +35 -0
- package/src/index.ts +69 -0
- package/src/shared.ts +29 -0
- package/src/styles.css +1 -0
- package/src/tailwind.css +1 -0
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import Dial from "./Dial.svelte";
|
|
3
|
+
import {
|
|
4
|
+
defaultMacroRackMacros,
|
|
5
|
+
type MacroRackMacro,
|
|
6
|
+
} from "./composites";
|
|
7
|
+
|
|
8
|
+
export let columns: 2 | 3 | 4 | 8 = 4;
|
|
9
|
+
export let disabled = false;
|
|
10
|
+
export let label = "Macro rack";
|
|
11
|
+
export let macros: MacroRackMacro[] = defaultMacroRackMacros;
|
|
12
|
+
export let onMacroChange:
|
|
13
|
+
| ((id: string, value: number, macro: MacroRackMacro) => void)
|
|
14
|
+
| undefined = undefined;
|
|
15
|
+
|
|
16
|
+
let className = "";
|
|
17
|
+
export { className as class };
|
|
18
|
+
|
|
19
|
+
function handleMacroChange(macro: MacroRackMacro, value: number) {
|
|
20
|
+
macros = macros.map((item) =>
|
|
21
|
+
item.id === macro.id ? { ...item, value } : item,
|
|
22
|
+
);
|
|
23
|
+
onMacroChange?.(macro.id, value, macro);
|
|
24
|
+
}
|
|
25
|
+
</script>
|
|
26
|
+
|
|
27
|
+
<div
|
|
28
|
+
{...$$restProps}
|
|
29
|
+
aria-label={label}
|
|
30
|
+
class={["macro-rack", className].filter(Boolean).join(" ")}
|
|
31
|
+
role="group"
|
|
32
|
+
style={`--macro-rack-columns: ${columns}`}
|
|
33
|
+
>
|
|
34
|
+
{#each macros as macro (macro.id)}
|
|
35
|
+
<Dial
|
|
36
|
+
class="macro-rack__control"
|
|
37
|
+
disabled={disabled || macro.disabled}
|
|
38
|
+
dragAxis={macro.dragAxis}
|
|
39
|
+
label={macro.label}
|
|
40
|
+
max={macro.max}
|
|
41
|
+
min={macro.min}
|
|
42
|
+
mode={macro.mode}
|
|
43
|
+
onValueChange={(value) => handleMacroChange(macro, value)}
|
|
44
|
+
step={macro.step}
|
|
45
|
+
value={macro.value}
|
|
46
|
+
/>
|
|
47
|
+
{/each}
|
|
48
|
+
</div>
|
package/src/Menu.svelte
ADDED
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { parseComponentProps, type MenuItem } from "@patchbayhq/ui";
|
|
3
|
+
|
|
4
|
+
export let value = "classic";
|
|
5
|
+
export let items: MenuItem[] = [
|
|
6
|
+
{ value: "classic", label: "Classic", disabled: false },
|
|
7
|
+
];
|
|
8
|
+
export let disabled = false;
|
|
9
|
+
export let onValueChange: ((value: string) => void) | undefined = undefined;
|
|
10
|
+
|
|
11
|
+
const id = `menu-${Math.random().toString(36).slice(2)}`;
|
|
12
|
+
let className = "";
|
|
13
|
+
let open = false;
|
|
14
|
+
let activeIndex = 0;
|
|
15
|
+
export { className as class };
|
|
16
|
+
|
|
17
|
+
$: props = parseComponentProps("menu", { disabled, items, value });
|
|
18
|
+
$: selectedIndex = Math.max(
|
|
19
|
+
0,
|
|
20
|
+
props.items.findIndex((item) => item.value === props.value),
|
|
21
|
+
);
|
|
22
|
+
$: selected = props.items[selectedIndex] ?? props.items[0];
|
|
23
|
+
|
|
24
|
+
function firstEnabledIndex(startIndex: number, direction: 1 | -1) {
|
|
25
|
+
for (let offset = 0; offset < props.items.length; offset += 1) {
|
|
26
|
+
const index =
|
|
27
|
+
(startIndex + offset * direction + props.items.length) %
|
|
28
|
+
props.items.length;
|
|
29
|
+
if (!props.items[index]?.disabled) return index;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return selectedIndex;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function commit(index: number) {
|
|
36
|
+
const item = props.items[index];
|
|
37
|
+
if (!item || item.disabled) return;
|
|
38
|
+
|
|
39
|
+
value = item.value;
|
|
40
|
+
activeIndex = index;
|
|
41
|
+
open = false;
|
|
42
|
+
onValueChange?.(value);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function toggleOpen() {
|
|
46
|
+
activeIndex = selectedIndex;
|
|
47
|
+
open = !open;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function handleKeydown(event: KeyboardEvent) {
|
|
51
|
+
if (event.key === "ArrowDown" || event.key === "ArrowUp") {
|
|
52
|
+
event.preventDefault();
|
|
53
|
+
const direction = event.key === "ArrowDown" ? 1 : -1;
|
|
54
|
+
activeIndex = firstEnabledIndex(activeIndex + direction, direction);
|
|
55
|
+
open = true;
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (event.key === "Enter" || event.key === " ") {
|
|
60
|
+
event.preventDefault();
|
|
61
|
+
if (open) {
|
|
62
|
+
commit(activeIndex);
|
|
63
|
+
} else {
|
|
64
|
+
open = true;
|
|
65
|
+
}
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (event.key === "Escape") {
|
|
70
|
+
open = false;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function handleBlur(event: FocusEvent) {
|
|
75
|
+
const nextTarget = event.relatedTarget;
|
|
76
|
+
if (
|
|
77
|
+
nextTarget instanceof Node &&
|
|
78
|
+
event.currentTarget instanceof HTMLElement &&
|
|
79
|
+
event.currentTarget.contains(nextTarget)
|
|
80
|
+
) {
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
open = false;
|
|
85
|
+
}
|
|
86
|
+
</script>
|
|
87
|
+
|
|
88
|
+
<div
|
|
89
|
+
class={["menu", className].filter(Boolean).join(" ")}
|
|
90
|
+
data-open={open ? "true" : undefined}
|
|
91
|
+
on:blur={handleBlur}
|
|
92
|
+
>
|
|
93
|
+
<button
|
|
94
|
+
aria-controls={`${id}-listbox`}
|
|
95
|
+
aria-expanded={open}
|
|
96
|
+
aria-haspopup="listbox"
|
|
97
|
+
class="menu__button"
|
|
98
|
+
{disabled}
|
|
99
|
+
id={`${id}-button`}
|
|
100
|
+
type="button"
|
|
101
|
+
on:click={toggleOpen}
|
|
102
|
+
on:keydown={handleKeydown}
|
|
103
|
+
>
|
|
104
|
+
<span class="menu__value">{selected?.label ?? "Select"}</span>
|
|
105
|
+
<span aria-hidden="true" class="menu__chevron"></span>
|
|
106
|
+
</button>
|
|
107
|
+
|
|
108
|
+
{#if open}
|
|
109
|
+
<div
|
|
110
|
+
aria-activedescendant={`${id}-option-${activeIndex}`}
|
|
111
|
+
class="menu__list"
|
|
112
|
+
id={`${id}-listbox`}
|
|
113
|
+
role="listbox"
|
|
114
|
+
tabindex="-1"
|
|
115
|
+
>
|
|
116
|
+
{#each props.items as item, index (item.value)}
|
|
117
|
+
<button
|
|
118
|
+
aria-disabled={item.disabled ? "true" : undefined}
|
|
119
|
+
aria-selected={item.value === props.value}
|
|
120
|
+
class={[
|
|
121
|
+
"menu__option",
|
|
122
|
+
index === activeIndex && "is-active",
|
|
123
|
+
item.value === props.value && "is-selected",
|
|
124
|
+
]
|
|
125
|
+
.filter(Boolean)
|
|
126
|
+
.join(" ")}
|
|
127
|
+
disabled={item.disabled}
|
|
128
|
+
id={`${id}-option-${index}`}
|
|
129
|
+
role="option"
|
|
130
|
+
type="button"
|
|
131
|
+
on:click={() => commit(index)}
|
|
132
|
+
on:mouseenter={() => (activeIndex = index)}
|
|
133
|
+
>
|
|
134
|
+
{item.label}
|
|
135
|
+
</button>
|
|
136
|
+
{/each}
|
|
137
|
+
</div>
|
|
138
|
+
{/if}
|
|
139
|
+
</div>
|
package/src/Meter.svelte
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { parseComponentProps, type Orientation } from "@patchbayhq/ui";
|
|
3
|
+
|
|
4
|
+
export let level = 0;
|
|
5
|
+
export let peak: number | undefined = undefined;
|
|
6
|
+
export let orientation: Orientation = "vertical";
|
|
7
|
+
|
|
8
|
+
let className = "";
|
|
9
|
+
export { className as class };
|
|
10
|
+
|
|
11
|
+
$: props = parseComponentProps("meter", { level, orientation, peak });
|
|
12
|
+
</script>
|
|
13
|
+
|
|
14
|
+
<div aria-label="Signal meter" class={["meter", className].filter(Boolean).join(" ")} data-meter>
|
|
15
|
+
<span
|
|
16
|
+
aria-hidden="true"
|
|
17
|
+
class="meter__bar"
|
|
18
|
+
data-meter-bar
|
|
19
|
+
style={`--meter-value: ${props.level}`}
|
|
20
|
+
></span>
|
|
21
|
+
</div>
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { parseComponentProps, type NumberFormat } from "@patchbayhq/ui";
|
|
3
|
+
|
|
4
|
+
export let label = "Number";
|
|
5
|
+
export let value = 0;
|
|
6
|
+
export let min = 0;
|
|
7
|
+
export let max = 127;
|
|
8
|
+
export let step = 1;
|
|
9
|
+
export let format: NumberFormat = "integer";
|
|
10
|
+
export let disabled = false;
|
|
11
|
+
export let onValueChange: ((value: number) => void) | undefined = undefined;
|
|
12
|
+
|
|
13
|
+
let className = "";
|
|
14
|
+
export { className as class };
|
|
15
|
+
|
|
16
|
+
$: props = parseComponentProps("number-box", { disabled, format, label, max, min, step, value });
|
|
17
|
+
|
|
18
|
+
function handleInput(event: Event) {
|
|
19
|
+
value = Number((event.currentTarget as HTMLInputElement).value);
|
|
20
|
+
onValueChange?.(value);
|
|
21
|
+
}
|
|
22
|
+
</script>
|
|
23
|
+
|
|
24
|
+
<input
|
|
25
|
+
aria-label={props.label}
|
|
26
|
+
class={["number-box", className].filter(Boolean).join(" ")}
|
|
27
|
+
disabled={props.disabled}
|
|
28
|
+
max={props.max}
|
|
29
|
+
min={props.min}
|
|
30
|
+
step={props.step}
|
|
31
|
+
type="number"
|
|
32
|
+
bind:value
|
|
33
|
+
on:input={handleInput}
|
|
34
|
+
/>
|
package/src/Panel.svelte
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
export let compact = false;
|
|
3
|
+
|
|
4
|
+
let className = "";
|
|
5
|
+
export { className as class };
|
|
6
|
+
</script>
|
|
7
|
+
|
|
8
|
+
<section
|
|
9
|
+
{...$$restProps}
|
|
10
|
+
class={["panel", compact && "panel--compact", className].filter(Boolean).join(" ")}
|
|
11
|
+
>
|
|
12
|
+
<slot />
|
|
13
|
+
</section>
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
export let eyebrow: string | undefined = undefined;
|
|
3
|
+
export let subtitle: string | undefined = undefined;
|
|
4
|
+
export let title: string | undefined = undefined;
|
|
5
|
+
|
|
6
|
+
let className = "";
|
|
7
|
+
export { className as class };
|
|
8
|
+
</script>
|
|
9
|
+
|
|
10
|
+
<header
|
|
11
|
+
{...$$restProps}
|
|
12
|
+
class={["panel-header", className].filter(Boolean).join(" ")}
|
|
13
|
+
>
|
|
14
|
+
<div class="panel-header__main">
|
|
15
|
+
{#if eyebrow}
|
|
16
|
+
<span class="panel-header__eyebrow">{eyebrow}</span>
|
|
17
|
+
{/if}
|
|
18
|
+
{#if title}
|
|
19
|
+
<h3 class="panel-header__title">{title}</h3>
|
|
20
|
+
{/if}
|
|
21
|
+
{#if subtitle}
|
|
22
|
+
<p class="panel-header__subtitle">{subtitle}</p>
|
|
23
|
+
{/if}
|
|
24
|
+
</div>
|
|
25
|
+
<div class="panel-header__actions">
|
|
26
|
+
<slot />
|
|
27
|
+
</div>
|
|
28
|
+
</header>
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
export let title: string | undefined = undefined;
|
|
3
|
+
|
|
4
|
+
let className = "";
|
|
5
|
+
export { className as class };
|
|
6
|
+
</script>
|
|
7
|
+
|
|
8
|
+
<section
|
|
9
|
+
{...$$restProps}
|
|
10
|
+
class={["panel-section", className].filter(Boolean).join(" ")}
|
|
11
|
+
>
|
|
12
|
+
{#if title}
|
|
13
|
+
<div class="panel-section__header">
|
|
14
|
+
<h4 class="panel-section__title">{title}</h4>
|
|
15
|
+
<div class="panel-section__actions">
|
|
16
|
+
<slot name="actions" />
|
|
17
|
+
</div>
|
|
18
|
+
</div>
|
|
19
|
+
{/if}
|
|
20
|
+
<div class="panel-section__body">
|
|
21
|
+
<slot />
|
|
22
|
+
</div>
|
|
23
|
+
</section>
|
package/src/Scope.svelte
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { onMount } from "svelte";
|
|
3
|
+
import { parseComponentProps } from "@patchbayhq/ui";
|
|
4
|
+
|
|
5
|
+
export let samples: number[] = [];
|
|
6
|
+
export let mode: "waveform" | "lissajous" = "waveform";
|
|
7
|
+
export let frozen = false;
|
|
8
|
+
|
|
9
|
+
let className = "";
|
|
10
|
+
let canvas: HTMLCanvasElement;
|
|
11
|
+
export { className as class };
|
|
12
|
+
|
|
13
|
+
$: props = parseComponentProps("scope", { frozen, mode, samples });
|
|
14
|
+
$: if (canvas && props) draw();
|
|
15
|
+
|
|
16
|
+
function draw() {
|
|
17
|
+
const context = canvas.getContext("2d");
|
|
18
|
+
if (!context) return;
|
|
19
|
+
|
|
20
|
+
context.clearRect(0, 0, canvas.width, canvas.height);
|
|
21
|
+
context.fillStyle = "#161616";
|
|
22
|
+
context.fillRect(0, 0, canvas.width, canvas.height);
|
|
23
|
+
context.strokeStyle = "rgba(255, 255, 255, 0.12)";
|
|
24
|
+
context.lineWidth = 1;
|
|
25
|
+
|
|
26
|
+
for (let x = 0; x < canvas.width; x += 23) {
|
|
27
|
+
context.beginPath();
|
|
28
|
+
context.moveTo(x, 0);
|
|
29
|
+
context.lineTo(x, canvas.height);
|
|
30
|
+
context.stroke();
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const wave = props.samples.length > 0 ? props.samples : Array.from({ length: canvas.width }, () => 0);
|
|
34
|
+
context.strokeStyle = "#9cd8ca";
|
|
35
|
+
context.lineWidth = 2;
|
|
36
|
+
context.beginPath();
|
|
37
|
+
|
|
38
|
+
wave.forEach((sample, index) => {
|
|
39
|
+
const x = (index / Math.max(1, wave.length - 1)) * canvas.width;
|
|
40
|
+
const y = canvas.height / 2 - Math.max(-1, Math.min(1, sample)) * (canvas.height * 0.38);
|
|
41
|
+
index === 0 ? context.moveTo(x, y) : context.lineTo(x, y);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
context.stroke();
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
onMount(draw);
|
|
48
|
+
</script>
|
|
49
|
+
|
|
50
|
+
<canvas bind:this={canvas} aria-label="Scope" class={["scope", className].filter(Boolean).join(" ")} data-scope width="184" height="98"></canvas>
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { parseComponentProps, type Orientation } from "@patchbayhq/ui";
|
|
3
|
+
import { init } from "./shared";
|
|
4
|
+
|
|
5
|
+
export let label = "Slider";
|
|
6
|
+
export let value = 0;
|
|
7
|
+
export let min = 0;
|
|
8
|
+
export let max = 1;
|
|
9
|
+
export let step: number | "any" = 0.01;
|
|
10
|
+
export let orientation: Orientation = "vertical";
|
|
11
|
+
export let modulation: number | undefined = undefined;
|
|
12
|
+
export let disabled = false;
|
|
13
|
+
export let onValueChange: ((value: number) => void) | undefined = undefined;
|
|
14
|
+
|
|
15
|
+
let className = "";
|
|
16
|
+
export { className as class };
|
|
17
|
+
|
|
18
|
+
$: props = parseComponentProps("slider", {
|
|
19
|
+
disabled,
|
|
20
|
+
label,
|
|
21
|
+
max,
|
|
22
|
+
min,
|
|
23
|
+
modulation,
|
|
24
|
+
orientation,
|
|
25
|
+
step,
|
|
26
|
+
value,
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
function handleInput(event: Event) {
|
|
30
|
+
value = Number((event.currentTarget as HTMLInputElement).value);
|
|
31
|
+
onValueChange?.(value);
|
|
32
|
+
}
|
|
33
|
+
</script>
|
|
34
|
+
|
|
35
|
+
<label class={["field", className].filter(Boolean).join(" ")}>
|
|
36
|
+
<span class="field__label">{props.label}</span>
|
|
37
|
+
<span
|
|
38
|
+
class="slider"
|
|
39
|
+
data-slider
|
|
40
|
+
data-orientation={props.orientation}
|
|
41
|
+
data-modulation={props.modulation}
|
|
42
|
+
use:init={[props.value, props.modulation, props.orientation]}
|
|
43
|
+
>
|
|
44
|
+
<input
|
|
45
|
+
aria-label={props.label}
|
|
46
|
+
type="range"
|
|
47
|
+
min={props.min}
|
|
48
|
+
max={props.max}
|
|
49
|
+
step={props.step}
|
|
50
|
+
bind:value
|
|
51
|
+
{disabled}
|
|
52
|
+
on:input={handleInput}
|
|
53
|
+
/>
|
|
54
|
+
</span>
|
|
55
|
+
</label>
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { StatusTone } from "./composites";
|
|
3
|
+
|
|
4
|
+
export let label = "";
|
|
5
|
+
export let pulse = false;
|
|
6
|
+
export let tone: StatusTone = "idle";
|
|
7
|
+
export let value = "";
|
|
8
|
+
|
|
9
|
+
let className = "";
|
|
10
|
+
export { className as class };
|
|
11
|
+
</script>
|
|
12
|
+
|
|
13
|
+
<span
|
|
14
|
+
{...$$restProps}
|
|
15
|
+
class={["status-indicator", className].filter(Boolean).join(" ")}
|
|
16
|
+
data-pulse={pulse ? "true" : undefined}
|
|
17
|
+
data-tone={tone}
|
|
18
|
+
>
|
|
19
|
+
<span aria-hidden="true" class="status-indicator__dot"></span>
|
|
20
|
+
{#if label}
|
|
21
|
+
<span class="status-indicator__label">{label}</span>
|
|
22
|
+
{/if}
|
|
23
|
+
{#if value}
|
|
24
|
+
<strong class="status-indicator__value">{value}</strong>
|
|
25
|
+
{/if}
|
|
26
|
+
</span>
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import {
|
|
3
|
+
defineElements,
|
|
4
|
+
parseComponentProps,
|
|
5
|
+
type StepChangeDetail,
|
|
6
|
+
type StepKey,
|
|
7
|
+
type StepLoop,
|
|
8
|
+
type StepNote,
|
|
9
|
+
} from "@patchbayhq/ui";
|
|
10
|
+
import { onMount } from "svelte";
|
|
11
|
+
|
|
12
|
+
export let activeKey: StepKey = "C4";
|
|
13
|
+
export let notes: StepNote[] = [];
|
|
14
|
+
export let loop: StepLoop = { start: 1, end: 16 };
|
|
15
|
+
export let bars = 4;
|
|
16
|
+
export let onChange: ((detail: StepChangeDetail) => void) | undefined = undefined;
|
|
17
|
+
export let onCellChange:
|
|
18
|
+
| ((change: Extract<StepChangeDetail, { type: "cell" }>) => void)
|
|
19
|
+
| undefined = undefined;
|
|
20
|
+
export let onKeyChange:
|
|
21
|
+
| ((change: Extract<StepChangeDetail, { type: "key" }>) => void)
|
|
22
|
+
| undefined = undefined;
|
|
23
|
+
export let onKeyPress: ((key: StepKey) => void) | undefined = undefined;
|
|
24
|
+
export let onKeyRelease: ((key: StepKey) => void) | undefined = undefined;
|
|
25
|
+
export let onLoopChange: ((loop: StepLoop) => void) | undefined = undefined;
|
|
26
|
+
export let onNotesChange: ((notes: StepNote[]) => void) | undefined = undefined;
|
|
27
|
+
|
|
28
|
+
let className = "";
|
|
29
|
+
let element: HTMLElement & {
|
|
30
|
+
activeKey: StepKey;
|
|
31
|
+
bars: number;
|
|
32
|
+
loop: StepLoop;
|
|
33
|
+
notes: StepNote[];
|
|
34
|
+
};
|
|
35
|
+
export { className as class };
|
|
36
|
+
|
|
37
|
+
$: props = parseComponentProps("step-sequencer", { activeKey, bars, loop, notes });
|
|
38
|
+
$: if (element) syncElement();
|
|
39
|
+
|
|
40
|
+
function syncElement() {
|
|
41
|
+
element.activeKey = props.activeKey;
|
|
42
|
+
element.bars = props.bars;
|
|
43
|
+
element.loop = props.loop;
|
|
44
|
+
element.notes = props.notes;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function handleChange(event: Event) {
|
|
48
|
+
const detail = (event as CustomEvent<StepChangeDetail>).detail;
|
|
49
|
+
activeKey = detail.activeKey;
|
|
50
|
+
loop = detail.loop;
|
|
51
|
+
notes = detail.notes;
|
|
52
|
+
onChange?.(detail);
|
|
53
|
+
if (detail.type === "key") {
|
|
54
|
+
onKeyChange?.(detail);
|
|
55
|
+
if (detail.pressed) {
|
|
56
|
+
onKeyPress?.(detail.key);
|
|
57
|
+
} else {
|
|
58
|
+
onKeyRelease?.(detail.key);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
if (detail.type === "loop") onLoopChange?.(detail.loop);
|
|
62
|
+
if (detail.type === "cell") {
|
|
63
|
+
onCellChange?.(detail);
|
|
64
|
+
onNotesChange?.(notes);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
onMount(() => {
|
|
69
|
+
defineElements();
|
|
70
|
+
syncElement();
|
|
71
|
+
element.addEventListener("step-change", handleChange);
|
|
72
|
+
|
|
73
|
+
return () => element.removeEventListener("step-change", handleChange);
|
|
74
|
+
});
|
|
75
|
+
</script>
|
|
76
|
+
|
|
77
|
+
<step-sequencer bind:this={element} class={className}></step-sequencer>
|
package/src/Tabs.svelte
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { parseComponentProps, type TabItem } from "@patchbayhq/ui";
|
|
3
|
+
|
|
4
|
+
export let value = "one";
|
|
5
|
+
export let items: TabItem[] = [{ value: "one", label: "One", disabled: false }];
|
|
6
|
+
export let disabled = false;
|
|
7
|
+
export let onValueChange: ((value: string) => void) | undefined = undefined;
|
|
8
|
+
|
|
9
|
+
let className = "";
|
|
10
|
+
export { className as class };
|
|
11
|
+
|
|
12
|
+
$: props = parseComponentProps("tabs", { disabled, items, value });
|
|
13
|
+
|
|
14
|
+
function select(nextValue: string) {
|
|
15
|
+
value = nextValue;
|
|
16
|
+
onValueChange?.(value);
|
|
17
|
+
}
|
|
18
|
+
</script>
|
|
19
|
+
|
|
20
|
+
<div class={["tabs", className].filter(Boolean).join(" ")} role="tablist">
|
|
21
|
+
{#each props.items as item (item.value)}
|
|
22
|
+
<button
|
|
23
|
+
aria-selected={item.value === props.value}
|
|
24
|
+
class={["tab", item.value === props.value ? "is-active" : ""].filter(Boolean).join(" ")}
|
|
25
|
+
disabled={props.disabled || item.disabled}
|
|
26
|
+
role="tab"
|
|
27
|
+
type="button"
|
|
28
|
+
on:click={() => select(item.value)}
|
|
29
|
+
>
|
|
30
|
+
{item.label}
|
|
31
|
+
</button>
|
|
32
|
+
{/each}
|
|
33
|
+
</div>
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import {
|
|
3
|
+
parseComponentProps,
|
|
4
|
+
type ControlAppearance,
|
|
5
|
+
} from "@patchbayhq/ui";
|
|
6
|
+
|
|
7
|
+
export let value = false;
|
|
8
|
+
export let labels = { off: "Off", on: "On" };
|
|
9
|
+
export let icon: string | undefined = undefined;
|
|
10
|
+
export let picture: string | undefined = undefined;
|
|
11
|
+
export let pictureAlt = "";
|
|
12
|
+
export let appearance: ControlAppearance = "default";
|
|
13
|
+
export let disabled = false;
|
|
14
|
+
export let onValueChange: ((value: boolean) => void) | undefined = undefined;
|
|
15
|
+
|
|
16
|
+
let className = "";
|
|
17
|
+
export { className as class };
|
|
18
|
+
|
|
19
|
+
$: props = parseComponentProps("text-button", {
|
|
20
|
+
appearance,
|
|
21
|
+
disabled,
|
|
22
|
+
icon,
|
|
23
|
+
labels,
|
|
24
|
+
picture,
|
|
25
|
+
pictureAlt,
|
|
26
|
+
value,
|
|
27
|
+
});
|
|
28
|
+
$: label = props.value ? props.labels.on : props.labels.off;
|
|
29
|
+
|
|
30
|
+
function toggle() {
|
|
31
|
+
value = !props.value;
|
|
32
|
+
onValueChange?.(value);
|
|
33
|
+
}
|
|
34
|
+
</script>
|
|
35
|
+
|
|
36
|
+
<button
|
|
37
|
+
aria-pressed={props.value}
|
|
38
|
+
class={["text-button", className].filter(Boolean).join(" ")}
|
|
39
|
+
data-appearance={props.appearance}
|
|
40
|
+
{disabled}
|
|
41
|
+
type="button"
|
|
42
|
+
on:click={toggle}
|
|
43
|
+
>
|
|
44
|
+
{#if props.picture}
|
|
45
|
+
<img
|
|
46
|
+
alt={props.pictureAlt}
|
|
47
|
+
class="text-button__picture"
|
|
48
|
+
src={props.picture}
|
|
49
|
+
/>
|
|
50
|
+
{:else if props.icon}
|
|
51
|
+
<span aria-hidden="true" class="text-button__icon">{props.icon}</span>
|
|
52
|
+
{/if}
|
|
53
|
+
<span class="text-button__label"><slot>{label}</slot></span>
|
|
54
|
+
</button>
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { parseComponentProps, type ControlAppearance } from "@patchbayhq/ui";
|
|
3
|
+
|
|
4
|
+
export let label = "Toggle";
|
|
5
|
+
export let checked = false;
|
|
6
|
+
export let appearance: ControlAppearance = "default";
|
|
7
|
+
export let disabled = false;
|
|
8
|
+
export let onCheckedChange: ((checked: boolean) => void) | undefined = undefined;
|
|
9
|
+
|
|
10
|
+
let className = "";
|
|
11
|
+
export { className as class };
|
|
12
|
+
|
|
13
|
+
$: props = parseComponentProps("toggle", {
|
|
14
|
+
appearance,
|
|
15
|
+
checked,
|
|
16
|
+
disabled,
|
|
17
|
+
label,
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
function handleChange(event: Event) {
|
|
21
|
+
checked = (event.currentTarget as HTMLInputElement).checked;
|
|
22
|
+
onCheckedChange?.(checked);
|
|
23
|
+
}
|
|
24
|
+
</script>
|
|
25
|
+
|
|
26
|
+
<label
|
|
27
|
+
class={["toggle", className].filter(Boolean).join(" ")}
|
|
28
|
+
data-appearance={props.appearance}
|
|
29
|
+
>
|
|
30
|
+
<input aria-label={props.label} type="checkbox" bind:checked {disabled} on:change={handleChange} />
|
|
31
|
+
<span aria-hidden="true"></span>
|
|
32
|
+
</label>
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
DialProps,
|
|
3
|
+
StepChangeDetail,
|
|
4
|
+
StepKey,
|
|
5
|
+
StepLoop,
|
|
6
|
+
StepNote,
|
|
7
|
+
} from "@patchbayhq/ui";
|
|
8
|
+
|
|
9
|
+
export type StatusTone = "idle" | "active" | "warning" | "danger";
|
|
10
|
+
|
|
11
|
+
export type MacroRackMacro = {
|
|
12
|
+
dragAxis?: DialProps["dragAxis"];
|
|
13
|
+
disabled?: boolean;
|
|
14
|
+
id: string;
|
|
15
|
+
label: string;
|
|
16
|
+
max?: number;
|
|
17
|
+
min?: number;
|
|
18
|
+
mode?: DialProps["mode"];
|
|
19
|
+
step?: DialProps["step"];
|
|
20
|
+
value: number;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export const defaultMacroRackMacros: MacroRackMacro[] = Array.from(
|
|
24
|
+
{ length: 8 },
|
|
25
|
+
(_, index) => ({
|
|
26
|
+
id: `macro-${index + 1}`,
|
|
27
|
+
label: `Macro ${index + 1}`,
|
|
28
|
+
value: 0,
|
|
29
|
+
}),
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
export type ClipStepLauncherChange = StepChangeDetail;
|
|
33
|
+
export type ClipStepLauncherKey = StepKey;
|
|
34
|
+
export type ClipStepLauncherLoop = StepLoop;
|
|
35
|
+
export type ClipStepLauncherNote = StepNote;
|