@erclx/canon 5.52.0 → 5.54.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/CHANGELOG.md +17 -0
- package/README.md +11 -13
- package/claude/.claude-plugin/plugin.json +1 -1
- package/claude/skills/canon-cli/SKILL.md +5 -8
- package/claude/skills/context-fold/REQUIREMENT.md +0 -1
- package/claude/skills/design-identity/REQUIREMENT.md +2 -2
- package/claude/skills/design-identity/SKILL.md +6 -6
- package/claude/skills/design-record/REQUIREMENT.md +2 -2
- package/claude/skills/design-record/SKILL.md +4 -4
- package/claude/skills/design-record/references/folder.md +1 -1
- package/claude/skills/design-taste/REQUIREMENT.md +1 -1
- package/claude/skills/design-taste/references/kinds.md +0 -1
- package/claude/skills/draw-figure/REQUIREMENT.md +3 -4
- package/claude/skills/draw-figure/SKILL.md +5 -6
- package/claude/skills/draw-figure/references/figures.md +0 -1
- package/claude/skills/draw-figure/references/hand-drawn.md +1 -1
- package/claude/skills/pick-by-eye/REQUIREMENT.md +4 -5
- package/claude/skills/pick-by-eye/SKILL.md +34 -41
- package/claude/skills/pick-by-eye/references/board.md +42 -0
- package/claude/skills/pick-by-eye/references/live-arms.md +3 -3
- package/claude/skills/plan-groundwork/SKILL.md +1 -1
- package/claude/skills/teach-craft/REQUIREMENT.md +30 -0
- package/claude/skills/teach-craft/SKILL.md +70 -0
- package/claude/skills/teach-craft/references/adopted.md +113 -0
- package/claude/skills/teach-craft/references/lesson-craft.md +60 -0
- package/claude/skills/{teach-workspace → teach-craft}/references/pedagogy.md +41 -6
- package/docs/agents/audits.md +0 -1
- package/docs/agents/install-and-sync.md +0 -8
- package/docs/target/migrations.md +16 -2
- package/docs/target/projects.md +1 -1
- package/docs/workflow/parallel-features.md +1 -1
- package/docs/workflow/skill-map-on-request.md +10 -22
- package/docs/workflow/skill-map.md +11 -14
- package/docs/workflow/visual-design-workflow.md +1 -4
- package/governance/detect.toml +1 -6
- package/governance/renames.toml +0 -1
- package/package.json +2 -8
- package/scripts/core/regen-web-previews.ts +2 -67
- package/src/audits/catalog.ts +0 -1
- package/src/browser/engine.ts +3 -6
- package/src/capture/render.ts +1 -1
- package/src/claude/cases/authoring.ts +0 -12
- package/src/claude/cases/misc.ts +0 -22
- package/src/claude/cases/workflow.ts +2 -38
- package/src/claude/skills-audit.ts +1 -1
- package/src/cli/help.ts +0 -10
- package/src/cli.ts +0 -8
- package/src/commands/design.ts +16 -315
- package/src/commands/records.ts +0 -1
- package/src/commands/serve.ts +2 -2
- package/src/commands/sync.ts +4 -5
- package/src/design/components.ts +8 -1514
- package/src/design/css.ts +5 -6
- package/src/design/fonts.ts +3 -5
- package/src/design/regen.ts +5 -32
- package/src/design/render.ts +1 -1
- package/src/design/tokens.ts +3 -2
- package/src/driver/drive.ts +3 -4
- package/src/driver/steps.ts +2 -5
- package/src/gate/stages.ts +4 -10
- package/src/gov/audit.ts +1 -6
- package/src/inventory/walk.ts +1 -1
- package/src/markdown/scope.ts +6 -35
- package/src/records/validate.ts +4 -176
- package/src/sandbox/equivalence.ts +0 -5
- package/src/shipped/references.ts +1 -2
- package/src/sync/check.ts +4 -29
- package/src/sync/stamp.ts +6 -6
- package/src/sync/target.ts +1 -4
- package/tooling/claude/reference.md +1 -1
- package/tooling/claude/seeds/canon/DESIGN.md +1 -1
- package/claude/skills/canvas-board/REQUIREMENT.md +0 -35
- package/claude/skills/canvas-board/SKILL.md +0 -143
- package/claude/skills/canvas-board/references/canvas.md +0 -170
- package/claude/skills/draft-and-pick/REQUIREMENT.md +0 -26
- package/claude/skills/draft-and-pick/SKILL.md +0 -11
- package/claude/skills/draft-doc/REQUIREMENT.md +0 -26
- package/claude/skills/draft-doc/SKILL.md +0 -11
- package/claude/skills/draft-figure/REQUIREMENT.md +0 -26
- package/claude/skills/draft-figure/SKILL.md +0 -11
- package/claude/skills/draft-identity/REQUIREMENT.md +0 -26
- package/claude/skills/draft-identity/SKILL.md +0 -11
- package/claude/skills/draft-ready/REQUIREMENT.md +0 -26
- package/claude/skills/draft-ready/SKILL.md +0 -11
- package/claude/skills/draft-screencast/REQUIREMENT.md +0 -26
- package/claude/skills/draft-screencast/SKILL.md +0 -11
- package/claude/skills/draft-slides/REQUIREMENT.md +0 -26
- package/claude/skills/draft-slides/SKILL.md +0 -11
- package/claude/skills/make-deck/REQUIREMENT.md +0 -36
- package/claude/skills/make-deck/SKILL.md +0 -80
- package/claude/skills/make-deck/references/deck.md +0 -183
- package/claude/skills/read-frames/REQUIREMENT.md +0 -30
- package/claude/skills/read-frames/SKILL.md +0 -55
- package/claude/skills/record-screencast/REQUIREMENT.md +0 -35
- package/claude/skills/record-screencast/SKILL.md +0 -99
- package/claude/skills/script-screencast/REQUIREMENT.md +0 -34
- package/claude/skills/script-screencast/SKILL.md +0 -70
- package/claude/skills/script-screencast/references/beats-template.md +0 -119
- package/claude/skills/teach-workspace/REQUIREMENT.md +0 -34
- package/claude/skills/teach-workspace/SKILL.md +0 -196
- package/claude/skills/teach-workspace/references/glossary.md +0 -90
- package/claude/skills/teach-workspace/references/lesson-build.md +0 -106
- package/claude/skills/teach-workspace/references/lesson-craft.md +0 -135
- package/claude/skills/teach-workspace/references/promotion-fold.md +0 -35
- package/claude/skills/teach-workspace/references/promotion.md +0 -116
- package/claude/skills/teach-workspace/references/revisit.md +0 -28
- package/claude/skills/teach-workspace/references/teach.md +0 -211
- package/claude/skills/video-craft/REQUIREMENT.md +0 -35
- package/claude/skills/video-craft/SKILL.md +0 -96
- package/claude/skills/video-craft/references/adopted.md +0 -110
- package/governance/rules/ui/canvas.md +0 -13
- package/src/canvas/address.ts +0 -227
- package/src/canvas/arrange.ts +0 -66
- package/src/canvas/capture.ts +0 -391
- package/src/canvas/client/app.tsx +0 -188
- package/src/canvas/client/inspector/align-grid.tsx +0 -162
- package/src/canvas/client/inspector/color-field.tsx +0 -398
- package/src/canvas/client/inspector/color-inputs.tsx +0 -139
- package/src/canvas/client/inspector/color-picker.tsx +0 -668
- package/src/canvas/client/inspector/color-space.ts +0 -240
- package/src/canvas/client/inspector/field.tsx +0 -263
- package/src/canvas/client/inspector/fields.css +0 -763
- package/src/canvas/client/inspector/section.tsx +0 -27
- package/src/canvas/client/inspector/size-field.tsx +0 -69
- package/src/canvas/client/inspector/token-select.tsx +0 -109
- package/src/canvas/client/inspector/values.ts +0 -297
- package/src/canvas/client/inspector.tsx +0 -692
- package/src/canvas/client/keys.ts +0 -71
- package/src/canvas/client/layers.tsx +0 -118
- package/src/canvas/client/main.tsx +0 -71
- package/src/canvas/client/pages-panel.tsx +0 -212
- package/src/canvas/client/panel-handle.tsx +0 -90
- package/src/canvas/client/selection.tsx +0 -199
- package/src/canvas/client/shell.css +0 -762
- package/src/canvas/client/state.ts +0 -792
- package/src/canvas/client/surface.tsx +0 -803
- package/src/canvas/client/theme-panel.tsx +0 -55
- package/src/canvas/client/tools.tsx +0 -153
- package/src/canvas/content.ts +0 -781
- package/src/canvas/edit.ts +0 -575
- package/src/canvas/editing.ts +0 -203
- package/src/canvas/fit.ts +0 -238
- package/src/canvas/global.d.ts +0 -15
- package/src/canvas/history.ts +0 -116
- package/src/canvas/index.html +0 -20
- package/src/canvas/server.ts +0 -766
- package/src/canvas/tokens.ts +0 -221
- package/src/commands/canvas.ts +0 -1097
- package/src/commands/demo.ts +0 -542
- package/src/commands/slides.ts +0 -184
- package/src/commands/teach.ts +0 -1401
- package/src/demo/beats.ts +0 -135
- package/src/demo/compile.ts +0 -350
- package/src/demo/container.ts +0 -219
- package/src/demo/cursors.ts +0 -55
- package/src/demo/drive.ts +0 -442
- package/src/demo/pointer.ts +0 -178
- package/src/demo/theme.ts +0 -112
- package/src/demo/timeline.ts +0 -64
- package/src/design/adapter.ts +0 -59
- package/src/design/base.css +0 -203
- package/src/design/brand-favicon.ts +0 -18
- package/src/design/neutral.ts +0 -127
- package/src/design/project-fonts.ts +0 -392
- package/src/slides/convert/chart.ts +0 -165
- package/src/slides/convert/deck.ts +0 -244
- package/src/slides/convert/export.ts +0 -567
- package/src/slides/convert/master.ts +0 -198
- package/src/slides/convert/shapes.ts +0 -804
- package/src/slides/convert/svg.ts +0 -38
- package/src/slides/convert/walk.ts +0 -671
- package/src/slides/decks.ts +0 -110
- package/src/slides/open.ts +0 -18
- package/src/slides/package/fonts.ts +0 -301
- package/src/slides/package/motion.ts +0 -316
- package/src/slides/package/test-face.ts +0 -113
- package/src/teach/browser/compile.ts +0 -44
- package/src/teach/browser/dismiss.ts +0 -76
- package/src/teach/browser/focus-line.ts +0 -19
- package/src/teach/browser/glossary-filter.ts +0 -44
- package/src/teach/browser/head.ts +0 -30
- package/src/teach/browser/quiz.ts +0 -19
- package/src/teach/browser/reload.ts +0 -9
- package/src/teach/browser/sidebar.ts +0 -233
- package/src/teach/browser/theme.ts +0 -18
- package/src/teach/components/code.tsx +0 -24
- package/src/teach/components/heading.tsx +0 -11
- package/src/teach/components/list.tsx +0 -17
- package/src/teach/components/paragraph.tsx +0 -29
- package/src/teach/components/refs.tsx +0 -28
- package/src/teach/cross-references.ts +0 -78
- package/src/teach/fonts.ts +0 -16
- package/src/teach/highlight.ts +0 -71
- package/src/teach/html/jsx-dev-runtime.ts +0 -13
- package/src/teach/html/jsx-runtime.ts +0 -113
- package/src/teach/html/raw.ts +0 -28
- package/src/teach/lesson.ts +0 -178
- package/src/teach/nav.ts +0 -1337
- package/src/teach/render.ts +0 -240
- package/src/teach/workspace.ts +0 -1164
- package/src/teach/write-if-changed.ts +0 -17
|
@@ -1,240 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Conversions between the hex the writer takes and the spaces the picker
|
|
3
|
-
* edits in. Channels are 0 to 255, hues are degrees, and every other component
|
|
4
|
-
* is 0 to 100. LCH is CIE LCH as CSS `lch()` defines it, and the matrices are
|
|
5
|
-
* the ones CSS Color 4 publishes in its sample code.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
export interface Rgb {
|
|
9
|
-
readonly r: number
|
|
10
|
-
readonly g: number
|
|
11
|
-
readonly b: number
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export interface Hsv {
|
|
15
|
-
readonly h: number
|
|
16
|
-
readonly s: number
|
|
17
|
-
readonly v: number
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export interface Hsl {
|
|
21
|
-
readonly h: number
|
|
22
|
-
readonly s: number
|
|
23
|
-
readonly l: number
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export interface Lch {
|
|
27
|
-
readonly l: number
|
|
28
|
-
readonly c: number
|
|
29
|
-
readonly h: number
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
type Vector = readonly [number, number, number]
|
|
33
|
-
type Matrix = readonly [Vector, Vector, Vector]
|
|
34
|
-
|
|
35
|
-
const LINEAR_SRGB_TO_XYZ: Matrix = [
|
|
36
|
-
[506752 / 1228815, 87881 / 245763, 12673 / 70218],
|
|
37
|
-
[87098 / 409605, 175762 / 245763, 12673 / 175545],
|
|
38
|
-
[7918 / 409605, 87881 / 737289, 1001167 / 1053270],
|
|
39
|
-
]
|
|
40
|
-
|
|
41
|
-
const XYZ_TO_LINEAR_SRGB: Matrix = [
|
|
42
|
-
[12831 / 3959, -329 / 214, -1974 / 3959],
|
|
43
|
-
[-851781 / 878810, 1648619 / 878810, 36519 / 878810],
|
|
44
|
-
[705 / 12673, -2585 / 12673, 705 / 667],
|
|
45
|
-
]
|
|
46
|
-
|
|
47
|
-
/** Bradford chromatic adaptation, since sRGB is D65 and `lch()` is D50. */
|
|
48
|
-
const D65_TO_D50: Matrix = [
|
|
49
|
-
[1.0479297925449969, 0.022946870601609652, -0.05019226628920524],
|
|
50
|
-
[0.02962780877005599, 0.9904344267538799, -0.017073799063418826],
|
|
51
|
-
[-0.009243040646204504, 0.015055191490298152, 0.7518742814281371],
|
|
52
|
-
]
|
|
53
|
-
|
|
54
|
-
const D50_TO_D65: Matrix = [
|
|
55
|
-
[0.955473421488075, -0.02309845494876471, 0.06325924320057072],
|
|
56
|
-
[-0.0283697093338637, 1.0099953980813041, 0.021041441191917323],
|
|
57
|
-
[0.012314014864481998, -0.020507649298898964, 1.330365926242124],
|
|
58
|
-
]
|
|
59
|
-
|
|
60
|
-
const D50_WHITE: Vector = [0.3457 / 0.3585, 1, (1 - 0.3457 - 0.3585) / 0.3585]
|
|
61
|
-
|
|
62
|
-
const EPSILON = 216 / 24389
|
|
63
|
-
const KAPPA = 24389 / 27
|
|
64
|
-
|
|
65
|
-
/** Chroma below this reads as no hue, which rounding noise never reaches. */
|
|
66
|
-
const ACHROMATIC = 0.02
|
|
67
|
-
|
|
68
|
-
function multiply(matrix: Matrix, vector: Vector): Vector {
|
|
69
|
-
const [row0, row1, row2] = matrix.map(
|
|
70
|
-
(row) => row[0] * vector[0] + row[1] * vector[1] + row[2] * vector[2],
|
|
71
|
-
)
|
|
72
|
-
return [row0 ?? 0, row1 ?? 0, row2 ?? 0]
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
function clamp(value: number, low: number, high: number): number {
|
|
76
|
-
return Math.min(high, Math.max(low, value))
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
function wrapHue(hue: number): number {
|
|
80
|
-
return ((hue % 360) + 360) % 360
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
export function hexToRgb(hex: string): Rgb {
|
|
84
|
-
return {
|
|
85
|
-
r: parseInt(hex.slice(0, 2), 16),
|
|
86
|
-
g: parseInt(hex.slice(2, 4), 16),
|
|
87
|
-
b: parseInt(hex.slice(4, 6), 16),
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
export function rgbToHex({ r, g, b }: Rgb): string {
|
|
92
|
-
return [r, g, b]
|
|
93
|
-
.map((part) =>
|
|
94
|
-
Math.round(clamp(part, 0, 255))
|
|
95
|
-
.toString(16)
|
|
96
|
-
.padStart(2, '0'),
|
|
97
|
-
)
|
|
98
|
-
.join('')
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
/**
|
|
102
|
-
* HSV off RGB. A color with no saturation has no hue of its own, so it takes
|
|
103
|
-
* the one handed in, which keeps a grey's hue slider where the operator left it.
|
|
104
|
-
*/
|
|
105
|
-
export function rgbToHsv({ r, g, b }: Rgb, hue = 0): Hsv {
|
|
106
|
-
const max = Math.max(r, g, b)
|
|
107
|
-
const delta = max - Math.min(r, g, b)
|
|
108
|
-
return {
|
|
109
|
-
h: delta === 0 ? hue : hueOf(r, g, b, max, delta),
|
|
110
|
-
s: max === 0 ? 0 : (delta / max) * 100,
|
|
111
|
-
v: (max / 255) * 100,
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
export function hsvToRgb({ h, s, v }: Hsv): Rgb {
|
|
116
|
-
const value = (v / 100) * 255
|
|
117
|
-
const chroma = value * (s / 100)
|
|
118
|
-
return fromChroma(h, chroma, value - chroma)
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
/** HSL off RGB, taking the hue handed in when there is no saturation. */
|
|
122
|
-
export function rgbToHsl({ r, g, b }: Rgb, hue = 0): Hsl {
|
|
123
|
-
const max = Math.max(r, g, b)
|
|
124
|
-
const min = Math.min(r, g, b)
|
|
125
|
-
const delta = max - min
|
|
126
|
-
const lightness = (max + min) / 2 / 255
|
|
127
|
-
const saturation =
|
|
128
|
-
delta === 0 ? 0 : delta / 255 / (1 - Math.abs(2 * lightness - 1))
|
|
129
|
-
return {
|
|
130
|
-
h: delta === 0 ? hue : hueOf(r, g, b, max, delta),
|
|
131
|
-
s: saturation * 100,
|
|
132
|
-
l: lightness * 100,
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
export function hslToRgb({ h, s, l }: Hsl): Rgb {
|
|
137
|
-
const lightness = l / 100
|
|
138
|
-
const chroma = (1 - Math.abs(2 * lightness - 1)) * (s / 100) * 255
|
|
139
|
-
return fromChroma(h, chroma, lightness * 255 - chroma / 2)
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
function hueOf(
|
|
143
|
-
r: number,
|
|
144
|
-
g: number,
|
|
145
|
-
b: number,
|
|
146
|
-
max: number,
|
|
147
|
-
delta: number,
|
|
148
|
-
): number {
|
|
149
|
-
const sector =
|
|
150
|
-
max === r
|
|
151
|
-
? (g - b) / delta + (g < b ? 6 : 0)
|
|
152
|
-
: max === g
|
|
153
|
-
? (b - r) / delta + 2
|
|
154
|
-
: (r - g) / delta + 4
|
|
155
|
-
return sector * 60
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
function fromChroma(hue: number, chroma: number, base: number): Rgb {
|
|
159
|
-
const sector = wrapHue(hue) / 60
|
|
160
|
-
const second = chroma * (1 - Math.abs((sector % 2) - 1))
|
|
161
|
-
const [r, g, b]: Vector =
|
|
162
|
-
sector < 1
|
|
163
|
-
? [chroma, second, 0]
|
|
164
|
-
: sector < 2
|
|
165
|
-
? [second, chroma, 0]
|
|
166
|
-
: sector < 3
|
|
167
|
-
? [0, chroma, second]
|
|
168
|
-
: sector < 4
|
|
169
|
-
? [0, second, chroma]
|
|
170
|
-
: sector < 5
|
|
171
|
-
? [second, 0, chroma]
|
|
172
|
-
: [chroma, 0, second]
|
|
173
|
-
return { r: r + base, g: g + base, b: b + base }
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
function toLinear(channel: number): number {
|
|
177
|
-
const value = channel / 255
|
|
178
|
-
return value <= 0.04045 ? value / 12.92 : ((value + 0.055) / 1.055) ** 2.4
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
function fromLinear(value: number): number {
|
|
182
|
-
const encoded =
|
|
183
|
-
value <= 0.0031308 ? value * 12.92 : 1.055 * value ** (1 / 2.4) - 0.055
|
|
184
|
-
return encoded * 255
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
/** CIE LCH off RGB, taking the hue handed in when there is no chroma. */
|
|
188
|
-
export function rgbToLch({ r, g, b }: Rgb, hue = 0): Lch {
|
|
189
|
-
const xyz = multiply(
|
|
190
|
-
D65_TO_D50,
|
|
191
|
-
multiply(LINEAR_SRGB_TO_XYZ, [toLinear(r), toLinear(g), toLinear(b)]),
|
|
192
|
-
)
|
|
193
|
-
const f = (ratio: number): number =>
|
|
194
|
-
ratio > EPSILON ? Math.cbrt(ratio) : (KAPPA * ratio + 16) / 116
|
|
195
|
-
const fx = f(xyz[0] / D50_WHITE[0])
|
|
196
|
-
const fy = f(xyz[1] / D50_WHITE[1])
|
|
197
|
-
const fz = f(xyz[2] / D50_WHITE[2])
|
|
198
|
-
const a = 500 * (fx - fy)
|
|
199
|
-
const bAxis = 200 * (fy - fz)
|
|
200
|
-
const chroma = Math.sqrt(a * a + bAxis * bAxis)
|
|
201
|
-
return {
|
|
202
|
-
l: 116 * fy - 16,
|
|
203
|
-
c: chroma < ACHROMATIC ? 0 : chroma,
|
|
204
|
-
h:
|
|
205
|
-
chroma < ACHROMATIC
|
|
206
|
-
? hue
|
|
207
|
-
: wrapHue((Math.atan2(bAxis, a) * 180) / Math.PI),
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
/**
|
|
212
|
-
* RGB off CIE LCH. A color outside sRGB has no faithful hex, so each channel
|
|
213
|
-
* clamps and `isClamped` says so, letting the row show the color written.
|
|
214
|
-
*/
|
|
215
|
-
export function lchToRgb({ l, c, h }: Lch): {
|
|
216
|
-
readonly rgb: Rgb
|
|
217
|
-
readonly isClamped: boolean
|
|
218
|
-
} {
|
|
219
|
-
const radians = (h * Math.PI) / 180
|
|
220
|
-
const fy = (l + 16) / 116
|
|
221
|
-
const fx = fy + (c * Math.cos(radians)) / 500
|
|
222
|
-
const fz = fy - (c * Math.sin(radians)) / 200
|
|
223
|
-
const ratios: Vector = [
|
|
224
|
-
fx ** 3 > EPSILON ? fx ** 3 : (116 * fx - 16) / KAPPA,
|
|
225
|
-
l > KAPPA * EPSILON ? fy ** 3 : l / KAPPA,
|
|
226
|
-
fz ** 3 > EPSILON ? fz ** 3 : (116 * fz - 16) / KAPPA,
|
|
227
|
-
]
|
|
228
|
-
const xyz: Vector = [
|
|
229
|
-
ratios[0] * D50_WHITE[0],
|
|
230
|
-
ratios[1] * D50_WHITE[1],
|
|
231
|
-
ratios[2] * D50_WHITE[2],
|
|
232
|
-
]
|
|
233
|
-
const channels = multiply(XYZ_TO_LINEAR_SRGB, multiply(D50_TO_D65, xyz)).map(
|
|
234
|
-
fromLinear,
|
|
235
|
-
)
|
|
236
|
-
/* Half a step either side rounds into range, so it is not a clamp. */
|
|
237
|
-
const isClamped = channels.some((part) => part < -0.5 || part > 255.5)
|
|
238
|
-
const [r, g, b] = channels.map((part) => clamp(part, 0, 255))
|
|
239
|
-
return { rgb: { r: r ?? 0, g: g ?? 0, b: b ?? 0 }, isClamped }
|
|
240
|
-
}
|
|
@@ -1,263 +0,0 @@
|
|
|
1
|
-
/** @jsxImportSource preact */
|
|
2
|
-
import type { ComponentChildren, JSX } from 'preact'
|
|
3
|
-
import { useRef } from 'preact/hooks'
|
|
4
|
-
import { scrubStep } from '@/canvas/client/inspector/values'
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* One drag of a number field's glyph. Each call takes the value as the field
|
|
8
|
-
* shows it. Only `commit` writes, so a drag posts one edit at most.
|
|
9
|
-
*/
|
|
10
|
-
export interface ScrubSession {
|
|
11
|
-
readonly preview: (value: string) => void
|
|
12
|
-
readonly restore: () => void
|
|
13
|
-
readonly commit: (value: string) => void
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Opens a session at press, so what `restore` returns to is read before any
|
|
18
|
-
* preview, however often the panel renders during the drag.
|
|
19
|
-
*/
|
|
20
|
-
export interface Scrub {
|
|
21
|
-
readonly begin: () => ScrubSession
|
|
22
|
-
/** Holds a scrubbed number inside what the property accepts. */
|
|
23
|
-
readonly clamp: (value: number) => number
|
|
24
|
-
/** Scales each pixel's move, for a value whose useful range is small. */
|
|
25
|
-
readonly step?: number
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
interface FieldProps {
|
|
29
|
-
/** The full word, which names the input for assistive technology. */
|
|
30
|
-
readonly label: string
|
|
31
|
-
/** The short mark drawn inside the field, hidden from assistive technology. */
|
|
32
|
-
readonly glyph: string
|
|
33
|
-
readonly initial: string
|
|
34
|
-
readonly isBusy: boolean
|
|
35
|
-
readonly isWide?: boolean
|
|
36
|
-
readonly placeholder?: string
|
|
37
|
-
readonly onCommit: (value: string) => void
|
|
38
|
-
/** Makes the glyph a drag handle. Typing stays the keyboard path. */
|
|
39
|
-
readonly scrub?: Scrub
|
|
40
|
-
/** A control inside the field's box after the value, such as a mode menu. */
|
|
41
|
-
readonly trailing?: ComponentChildren
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
const NUMBER = /^-?\d*\.?\d+$/
|
|
45
|
-
|
|
46
|
-
interface Drag {
|
|
47
|
-
readonly pointerId: number
|
|
48
|
-
/** What the input held at press, which a cancel puts back. */
|
|
49
|
-
readonly typed: string
|
|
50
|
-
/** The number the drag started from, an empty field's placeholder included. */
|
|
51
|
-
readonly start: string
|
|
52
|
-
readonly session: ScrubSession
|
|
53
|
-
lastX: number
|
|
54
|
-
value: number
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
function fieldClass(isWide: boolean | undefined, modifier?: string): string {
|
|
58
|
-
return ['glyph-field', isWide ? 'is-wide' : '', modifier ?? '']
|
|
59
|
-
.filter(Boolean)
|
|
60
|
-
.join(' ')
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
* Commits on change, which a text input fires on Enter or on leaving it, and
|
|
65
|
-
* sends nothing when the value is what it started at. Escape puts it back.
|
|
66
|
-
*/
|
|
67
|
-
export function Field({
|
|
68
|
-
label,
|
|
69
|
-
glyph,
|
|
70
|
-
initial,
|
|
71
|
-
isBusy,
|
|
72
|
-
isWide,
|
|
73
|
-
placeholder,
|
|
74
|
-
onCommit,
|
|
75
|
-
scrub,
|
|
76
|
-
trailing,
|
|
77
|
-
}: FieldProps): JSX.Element {
|
|
78
|
-
const input = useRef<HTMLInputElement>(null)
|
|
79
|
-
const handle = useRef<HTMLSpanElement>(null)
|
|
80
|
-
const drag = useRef<Drag | null>(null)
|
|
81
|
-
|
|
82
|
-
const end = (pointerId: number): Drag | undefined => {
|
|
83
|
-
const held = drag.current
|
|
84
|
-
if (!held || held.pointerId !== pointerId) return undefined
|
|
85
|
-
drag.current = null
|
|
86
|
-
return held
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
/* An empty field scrubs from the number its placeholder shows. */
|
|
90
|
-
const scrubFrom = (value: string): string =>
|
|
91
|
-
value.trim() || placeholder?.trim() || ''
|
|
92
|
-
const isScrubbable = scrub !== undefined && NUMBER.test(scrubFrom(initial))
|
|
93
|
-
|
|
94
|
-
const handlers = isScrubbable
|
|
95
|
-
? {
|
|
96
|
-
onPointerDown: (event: PointerEvent) => {
|
|
97
|
-
const typed = input.current?.value ?? ''
|
|
98
|
-
const start = scrubFrom(typed)
|
|
99
|
-
if (isBusy || !NUMBER.test(start)) return
|
|
100
|
-
event.preventDefault()
|
|
101
|
-
/*
|
|
102
|
-
* Capture keeps the release coming here when the pointer leaves the
|
|
103
|
-
* glyph, the panel, or crosses into a frame's own document.
|
|
104
|
-
*/
|
|
105
|
-
try {
|
|
106
|
-
handle.current?.setPointerCapture(event.pointerId)
|
|
107
|
-
} catch {
|
|
108
|
-
/* A pointer the browser no longer tracks ends on cancel instead. */
|
|
109
|
-
}
|
|
110
|
-
drag.current = {
|
|
111
|
-
pointerId: event.pointerId,
|
|
112
|
-
typed,
|
|
113
|
-
start,
|
|
114
|
-
session: scrub.begin(),
|
|
115
|
-
lastX: event.clientX,
|
|
116
|
-
value: Number(start),
|
|
117
|
-
}
|
|
118
|
-
},
|
|
119
|
-
onPointerMove: (event: PointerEvent) => {
|
|
120
|
-
const held = drag.current
|
|
121
|
-
if (!held || held.pointerId !== event.pointerId) return
|
|
122
|
-
const moved = event.clientX - held.lastX
|
|
123
|
-
held.lastX = event.clientX
|
|
124
|
-
held.value = scrub.clamp(
|
|
125
|
-
Math.round(
|
|
126
|
-
(held.value +
|
|
127
|
-
moved * scrubStep(event.shiftKey) * (scrub.step ?? 1)) *
|
|
128
|
-
10,
|
|
129
|
-
) / 10,
|
|
130
|
-
)
|
|
131
|
-
const shown = String(held.value)
|
|
132
|
-
if (input.current) input.current.value = shown
|
|
133
|
-
held.session.preview(shown)
|
|
134
|
-
},
|
|
135
|
-
onPointerUp: (event: PointerEvent) => {
|
|
136
|
-
const held = end(event.pointerId)
|
|
137
|
-
if (!held) return
|
|
138
|
-
const shown = String(held.value)
|
|
139
|
-
if (shown === held.start) held.session.restore()
|
|
140
|
-
else held.session.commit(shown)
|
|
141
|
-
},
|
|
142
|
-
onPointerCancel: (event: PointerEvent) => {
|
|
143
|
-
const held = end(event.pointerId)
|
|
144
|
-
if (!held) return
|
|
145
|
-
if (input.current) input.current.value = held.typed
|
|
146
|
-
held.session.restore()
|
|
147
|
-
},
|
|
148
|
-
}
|
|
149
|
-
: {}
|
|
150
|
-
|
|
151
|
-
return (
|
|
152
|
-
<div class={fieldClass(isWide)}>
|
|
153
|
-
<span
|
|
154
|
-
ref={handle}
|
|
155
|
-
class={isScrubbable ? 'glyph is-scrub' : 'glyph'}
|
|
156
|
-
aria-hidden="true"
|
|
157
|
-
title={label}
|
|
158
|
-
{...handlers}
|
|
159
|
-
>
|
|
160
|
-
{glyph}
|
|
161
|
-
</span>
|
|
162
|
-
<input
|
|
163
|
-
ref={input}
|
|
164
|
-
class="glyph-field-input"
|
|
165
|
-
type="text"
|
|
166
|
-
aria-label={label}
|
|
167
|
-
title={initial}
|
|
168
|
-
defaultValue={initial}
|
|
169
|
-
placeholder={placeholder}
|
|
170
|
-
disabled={isBusy}
|
|
171
|
-
spellcheck={false}
|
|
172
|
-
onChange={(event) => {
|
|
173
|
-
const value = event.currentTarget.value.trim()
|
|
174
|
-
if (value !== initial.trim()) onCommit(value)
|
|
175
|
-
}}
|
|
176
|
-
onKeyDown={(event) => {
|
|
177
|
-
if (event.key === 'Escape') event.currentTarget.value = initial
|
|
178
|
-
}}
|
|
179
|
-
/>
|
|
180
|
-
{trailing}
|
|
181
|
-
</div>
|
|
182
|
-
)
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
interface SelectFieldProps {
|
|
186
|
-
readonly label: string
|
|
187
|
-
readonly glyph: string
|
|
188
|
-
/** Each option's value, which is also what it shows. */
|
|
189
|
-
readonly options: readonly string[]
|
|
190
|
-
readonly value: string
|
|
191
|
-
readonly isBusy: boolean
|
|
192
|
-
readonly isWide?: boolean
|
|
193
|
-
readonly onCommit: (value: string) => void
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
/**
|
|
197
|
-
* A value picked from a fixed list, drawn like a field with a chevron. The
|
|
198
|
-
* select is native, so its list opens above the panel's clipping. A current
|
|
199
|
-
* value outside the list stays as an option, so it shows rather than blanks.
|
|
200
|
-
*/
|
|
201
|
-
export function SelectField({
|
|
202
|
-
label,
|
|
203
|
-
glyph,
|
|
204
|
-
options,
|
|
205
|
-
value,
|
|
206
|
-
isBusy,
|
|
207
|
-
isWide,
|
|
208
|
-
onCommit,
|
|
209
|
-
}: SelectFieldProps): JSX.Element {
|
|
210
|
-
const shown = options.includes(value) ? options : [...options, value]
|
|
211
|
-
return (
|
|
212
|
-
<div class={fieldClass(isWide, 'is-select')}>
|
|
213
|
-
<span class="glyph" aria-hidden="true" title={label}>
|
|
214
|
-
{glyph}
|
|
215
|
-
</span>
|
|
216
|
-
<select
|
|
217
|
-
class="glyph-field-input glyph-select"
|
|
218
|
-
aria-label={label}
|
|
219
|
-
value={value}
|
|
220
|
-
disabled={isBusy}
|
|
221
|
-
onChange={(event) => {
|
|
222
|
-
const picked = event.currentTarget.value
|
|
223
|
-
if (picked !== value) onCommit(picked)
|
|
224
|
-
}}
|
|
225
|
-
>
|
|
226
|
-
{shown.map((option) => (
|
|
227
|
-
<option key={option} value={option}>
|
|
228
|
-
{option}
|
|
229
|
-
</option>
|
|
230
|
-
))}
|
|
231
|
-
</select>
|
|
232
|
-
<svg class="select-chevron" viewBox="0 0 16 16" aria-hidden="true">
|
|
233
|
-
<path d="M4.5 6.5 8 10l3.5-3.5" />
|
|
234
|
-
</svg>
|
|
235
|
-
</div>
|
|
236
|
-
)
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
interface ReadOnlyFieldProps {
|
|
240
|
-
readonly label: string
|
|
241
|
-
readonly glyph: string
|
|
242
|
-
readonly value: string
|
|
243
|
-
readonly isWide?: boolean
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
/** A value the panel shows and nothing here writes, drawn like a field. */
|
|
247
|
-
export function ReadOnlyField({
|
|
248
|
-
label,
|
|
249
|
-
glyph,
|
|
250
|
-
value,
|
|
251
|
-
isWide,
|
|
252
|
-
}: ReadOnlyFieldProps): JSX.Element {
|
|
253
|
-
return (
|
|
254
|
-
<div class={fieldClass(isWide, 'is-readonly')}>
|
|
255
|
-
<span class="glyph" aria-hidden="true" title={label}>
|
|
256
|
-
{glyph}
|
|
257
|
-
</span>
|
|
258
|
-
<output class="glyph-field-input" aria-label={label} title={value}>
|
|
259
|
-
{value}
|
|
260
|
-
</output>
|
|
261
|
-
</div>
|
|
262
|
-
)
|
|
263
|
-
}
|