@xsynaptic/sonic-ui 0.4.0 → 0.5.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 +2 -21
- package/dist/default/dat.js +50 -0
- package/dist/default/define/button.js +7 -0
- package/dist/default/define/envelope.js +7 -0
- package/dist/default/define/split.js +7 -0
- package/dist/default/define/switch.js +7 -0
- package/dist/default/define/waveform.js +7 -0
- package/dist/default/define/wavestrip.js +7 -0
- package/dist/default/define/xy.js +7 -0
- package/dist/default/define.js +7 -1
- package/dist/default/elements/button.js +165 -0
- package/dist/default/elements/dial.js +16 -22
- package/dist/default/elements/envelope.js +367 -0
- package/dist/default/elements/meter.js +149 -61
- package/dist/default/elements/number.js +21 -8
- package/dist/default/elements/radio-group.js +185 -0
- package/dist/default/elements/readout-claim.js +81 -0
- package/dist/default/elements/readout.js +35 -0
- package/dist/default/elements/segmented.js +9 -159
- package/dist/default/elements/slider.js +42 -51
- package/dist/default/elements/sonic-element.js +24 -13
- package/dist/default/elements/split.js +249 -0
- package/dist/default/elements/switch.js +279 -0
- package/dist/default/elements/value-element.js +691 -0
- package/dist/default/elements/{range-entry.js → value-entry.js} +3 -3
- package/dist/default/elements/wave-element.js +77 -0
- package/dist/default/elements/waveform.js +305 -0
- package/dist/default/elements/wavestrip.js +182 -0
- package/dist/default/elements/xy.js +473 -0
- package/dist/default/index.js +10 -2
- package/dist/default/lib/adsr-shape.js +108 -0
- package/dist/default/lib/ballistics.js +55 -0
- package/dist/default/lib/canvas-surface.js +192 -0
- package/dist/default/lib/copy-node.js +32 -2
- package/dist/default/lib/crossfade.js +22 -0
- package/dist/default/lib/distribute.js +215 -0
- package/dist/default/lib/drag-step.js +99 -0
- package/dist/default/lib/envelope-curve.js +9 -0
- package/dist/default/lib/field.js +94 -0
- package/dist/default/lib/focus-by-pointer.js +28 -0
- package/dist/default/lib/frame-timeline.js +62 -0
- package/dist/default/lib/hold.js +38 -0
- package/dist/default/lib/marker-band.js +36 -0
- package/dist/default/lib/marker-rider.js +114 -0
- package/dist/default/lib/math.js +12 -3
- package/dist/default/lib/modifier-press.js +11 -0
- package/dist/default/lib/number-list.js +6 -6
- package/dist/default/lib/pointer-drag.js +43 -0
- package/dist/default/lib/render.js +20 -5
- package/dist/default/lib/resample-peaks.js +36 -0
- package/dist/default/lib/strip-scene.js +48 -0
- package/dist/default/lib/taper.js +23 -17
- package/dist/default/lib/time-regions.js +8 -0
- package/dist/default/lib/tracking-clock.js +35 -0
- package/dist/default/lib/value-mapping.js +115 -0
- package/dist/default/lib/waveform-buckets.js +41 -0
- package/dist/dev/dat.d.ts +19 -0
- package/dist/dev/dat.js +51 -0
- package/dist/dev/dat.js.map +1 -0
- package/dist/dev/define/button.d.ts +2 -0
- package/dist/dev/define/button.js +8 -0
- package/dist/dev/define/button.js.map +1 -0
- package/dist/dev/define/dial.js.map +1 -1
- package/dist/dev/define/envelope.d.ts +2 -0
- package/dist/dev/define/envelope.js +8 -0
- package/dist/dev/define/envelope.js.map +1 -0
- package/dist/dev/define/meter.js.map +1 -1
- package/dist/dev/define/number.js.map +1 -1
- package/dist/dev/define/segmented.js.map +1 -1
- package/dist/dev/define/slider.js.map +1 -1
- package/dist/dev/define/split.d.ts +2 -0
- package/dist/dev/define/split.js +8 -0
- package/dist/dev/define/split.js.map +1 -0
- package/dist/dev/define/switch.d.ts +2 -0
- package/dist/dev/define/switch.js +8 -0
- package/dist/dev/define/switch.js.map +1 -0
- package/dist/dev/define/waveform.d.ts +2 -0
- package/dist/dev/define/waveform.js +8 -0
- package/dist/dev/define/waveform.js.map +1 -0
- package/dist/dev/define/wavestrip.d.ts +2 -0
- package/dist/dev/define/wavestrip.js +8 -0
- package/dist/dev/define/wavestrip.js.map +1 -0
- package/dist/dev/define/xy.d.ts +2 -0
- package/dist/dev/define/xy.js +8 -0
- package/dist/dev/define/xy.js.map +1 -0
- package/dist/dev/define.d.ts +15 -3
- package/dist/dev/define.js +7 -1
- package/dist/dev/elements/{key.d.ts → button.d.ts} +8 -6
- package/dist/dev/elements/button.js +166 -0
- package/dist/dev/elements/button.js.map +1 -0
- package/dist/dev/elements/dial.d.ts +6 -4
- package/dist/dev/elements/dial.js +16 -22
- package/dist/dev/elements/dial.js.map +1 -1
- package/dist/dev/elements/envelope.d.ts +36 -0
- package/dist/dev/elements/envelope.js +372 -0
- package/dist/dev/elements/envelope.js.map +1 -0
- package/dist/dev/elements/form-element.d.ts +1 -1
- package/dist/dev/elements/form-element.js.map +1 -1
- package/dist/dev/elements/meter.d.ts +10 -5
- package/dist/dev/elements/meter.js +149 -61
- package/dist/dev/elements/meter.js.map +1 -1
- package/dist/dev/elements/number.d.ts +7 -3
- package/dist/dev/elements/number.js +21 -8
- package/dist/dev/elements/number.js.map +1 -1
- package/dist/dev/elements/radio-group.d.ts +25 -0
- package/dist/dev/elements/radio-group.js +186 -0
- package/dist/dev/elements/radio-group.js.map +1 -0
- package/dist/dev/elements/readout-claim.js +82 -0
- package/dist/dev/elements/readout-claim.js.map +1 -0
- package/dist/dev/elements/readout.js +36 -0
- package/dist/dev/elements/readout.js.map +1 -0
- package/dist/dev/elements/segmented.d.ts +4 -10
- package/dist/dev/elements/segmented.js +9 -159
- package/dist/dev/elements/segmented.js.map +1 -1
- package/dist/dev/elements/slider.d.ts +6 -6
- package/dist/dev/elements/slider.js +42 -51
- package/dist/dev/elements/slider.js.map +1 -1
- package/dist/dev/elements/sonic-element.d.ts +4 -3
- package/dist/dev/elements/sonic-element.js +51 -22
- package/dist/dev/elements/sonic-element.js.map +1 -1
- package/dist/dev/elements/split.d.ts +21 -0
- package/dist/dev/elements/split.js +258 -0
- package/dist/dev/elements/split.js.map +1 -0
- package/dist/dev/elements/switch.d.ts +35 -0
- package/dist/dev/elements/switch.js +280 -0
- package/dist/dev/elements/switch.js.map +1 -0
- package/dist/dev/elements/value-element.d.ts +84 -0
- package/dist/dev/elements/value-element.js +692 -0
- package/dist/dev/elements/value-element.js.map +1 -0
- package/dist/dev/elements/{range-entry.js → value-entry.js} +4 -4
- package/dist/dev/elements/value-entry.js.map +1 -0
- package/dist/dev/elements/wave-element.d.ts +36 -0
- package/dist/dev/elements/wave-element.js +78 -0
- package/dist/dev/elements/wave-element.js.map +1 -0
- package/dist/dev/elements/waveform.d.ts +55 -0
- package/dist/dev/elements/waveform.js +306 -0
- package/dist/dev/elements/waveform.js.map +1 -0
- package/dist/dev/elements/wavestrip.d.ts +51 -0
- package/dist/dev/elements/wavestrip.js +183 -0
- package/dist/dev/elements/wavestrip.js.map +1 -0
- package/dist/dev/elements/xy.d.ts +62 -0
- package/dist/dev/elements/xy.js +474 -0
- package/dist/dev/elements/xy.js.map +1 -0
- package/dist/dev/index.d.ts +11 -2
- package/dist/dev/index.js +10 -2
- package/dist/dev/lib/adsr-shape.js +109 -0
- package/dist/dev/lib/adsr-shape.js.map +1 -0
- package/dist/dev/lib/ballistics.js +56 -0
- package/dist/dev/lib/ballistics.js.map +1 -0
- package/dist/dev/lib/canvas-surface.d.ts +25 -0
- package/dist/dev/lib/canvas-surface.js +193 -0
- package/dist/dev/lib/canvas-surface.js.map +1 -0
- package/dist/dev/lib/copy-node.js +32 -2
- package/dist/dev/lib/copy-node.js.map +1 -1
- package/dist/dev/lib/crossfade.d.ts +12 -0
- package/dist/dev/lib/crossfade.js +23 -0
- package/dist/dev/lib/crossfade.js.map +1 -0
- package/dist/dev/lib/distribute.d.ts +4 -0
- package/dist/dev/lib/distribute.js +216 -0
- package/dist/dev/lib/distribute.js.map +1 -0
- package/dist/dev/lib/drag-step.js +100 -0
- package/dist/dev/lib/drag-step.js.map +1 -0
- package/dist/dev/lib/envelope-curve.d.ts +4 -0
- package/dist/dev/lib/envelope-curve.js +10 -0
- package/dist/dev/lib/envelope-curve.js.map +1 -0
- package/dist/dev/lib/field.d.ts +4 -0
- package/dist/dev/lib/field.js +95 -0
- package/dist/dev/lib/field.js.map +1 -0
- package/dist/dev/lib/focus-by-pointer.js +29 -0
- package/dist/dev/lib/focus-by-pointer.js.map +1 -0
- package/dist/dev/lib/frame-timeline.js +63 -0
- package/dist/dev/lib/frame-timeline.js.map +1 -0
- package/dist/dev/lib/hold.js +39 -0
- package/dist/dev/lib/hold.js.map +1 -0
- package/dist/dev/lib/marker-band.js +37 -0
- package/dist/dev/lib/marker-band.js.map +1 -0
- package/dist/dev/lib/marker-rider.js +115 -0
- package/dist/dev/lib/marker-rider.js.map +1 -0
- package/dist/dev/lib/math.js +12 -3
- package/dist/dev/lib/math.js.map +1 -1
- package/dist/dev/lib/modifier-press.js +12 -0
- package/dist/dev/lib/modifier-press.js.map +1 -0
- package/dist/dev/lib/number-list.js +6 -6
- package/dist/dev/lib/number-list.js.map +1 -1
- package/dist/dev/lib/pointer-drag.js +44 -0
- package/dist/dev/lib/pointer-drag.js.map +1 -0
- package/dist/dev/lib/read-px-property.js.map +1 -1
- package/dist/dev/lib/render.js +20 -5
- package/dist/dev/lib/render.js.map +1 -1
- package/dist/dev/lib/resample-peaks.js +37 -0
- package/dist/dev/lib/resample-peaks.js.map +1 -0
- package/dist/dev/lib/strip-scene.js +49 -0
- package/dist/dev/lib/strip-scene.js.map +1 -0
- package/dist/dev/lib/taper.js +23 -17
- package/dist/dev/lib/taper.js.map +1 -1
- package/dist/dev/lib/time-regions.d.ts +4 -0
- package/dist/dev/lib/time-regions.js +9 -0
- package/dist/dev/lib/time-regions.js.map +1 -0
- package/dist/dev/lib/tracking-clock.js +36 -0
- package/dist/dev/lib/tracking-clock.js.map +1 -0
- package/dist/dev/lib/value-mapping.d.ts +23 -0
- package/dist/dev/lib/value-mapping.js +116 -0
- package/dist/dev/lib/value-mapping.js.map +1 -0
- package/dist/dev/lib/waveform-buckets.d.ts +9 -0
- package/dist/dev/lib/waveform-buckets.js +42 -0
- package/dist/dev/lib/waveform-buckets.js.map +1 -0
- package/dist/skins/ivory.css +1 -2
- package/dist/styles/button.css +212 -0
- package/dist/styles/controls.css +8 -1
- package/dist/styles/dial.css +88 -42
- package/dist/styles/envelope.css +159 -0
- package/dist/styles/led.css +36 -5
- package/dist/styles/material.css +277 -76
- package/dist/styles/meter.css +33 -48
- package/dist/styles/number.css +5 -1
- package/dist/styles/panel.css +59 -0
- package/dist/styles/ring.css +74 -0
- package/dist/styles/segmented.css +21 -48
- package/dist/styles/slider.css +175 -39
- package/dist/styles/switch.css +347 -0
- package/dist/styles/waveform.css +173 -0
- package/dist/styles/wavestrip.css +215 -0
- package/dist/styles/xy.css +204 -0
- package/package.json +14 -2
- package/dist/default/define/key.js +0 -7
- package/dist/default/elements/key.js +0 -169
- package/dist/default/elements/range-element.js +0 -546
- package/dist/default/lib/detent.js +0 -17
- package/dist/dev/define/key.d.ts +0 -2
- package/dist/dev/define/key.js +0 -8
- package/dist/dev/define/key.js.map +0 -1
- package/dist/dev/elements/key.js +0 -170
- package/dist/dev/elements/key.js.map +0 -1
- package/dist/dev/elements/range-element.d.ts +0 -64
- package/dist/dev/elements/range-element.js +0 -547
- package/dist/dev/elements/range-element.js.map +0 -1
- package/dist/dev/elements/range-entry.js.map +0 -1
- package/dist/dev/lib/detent.js +0 -18
- package/dist/dev/lib/detent.js.map +0 -1
- package/dist/styles/key.css +0 -242
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"distribute.js","names":[],"sources":["../../../src/lib/distribute.ts"],"sourcesContent":["import { clamp, trimFloat } from '#lib/math.ts';\n\nexport interface SplitMember {\n\tisFree: boolean;\n\tmax: number;\n\tmin: number;\n\tsnap: (value: number) => number;\n\tstepFrom: (value: number, direction: -1 | 1) => number;\n\tvalue: number;\n}\n\nexport type SplitMode = 'cascade' | 'equal' | 'proportional';\n\ninterface SplitMove {\n\tdirection?: -1 | 0 | 1;\n\tindex: number;\n\ttarget: number;\n}\n\ninterface SplitRule {\n\tmode: SplitMode;\n\ttotal: number;\n}\n\ninterface Pooled {\n\tindex: number;\n\troom: number;\n\tweight: number;\n}\n\nconst epsilon = 1e-9;\n\nfunction sumOf(values: ReadonlyArray<number>): number {\n\treturn values.reduce((total, value) => total + value, 0);\n}\n\nfunction others(members: ReadonlyArray<SplitMember>, index: number): Array<SplitMember> {\n\treturn members.filter((_member, at) => at !== index);\n}\n\nexport function splitLimits(\n\tmembers: ReadonlyArray<SplitMember>,\n\tindex: number,\n\ttotal: number,\n): [number, number] {\n\tconst member = members[index];\n\tif (!member) return [NaN, NaN];\n\n\tconst rest = others(members, index);\n\tconst free = rest.filter((other) => other.isFree);\n\tif (free.length === 0) return [member.value, member.value];\n\n\tconst fixed = sumOf(rest.filter((other) => !other.isFree).map((other) => other.value));\n\tconst room = total - fixed;\n\n\treturn [\n\t\tMath.max(member.min, room - sumOf(free.map((other) => other.max))),\n\t\tMath.min(member.max, room - sumOf(free.map((other) => other.min))),\n\t];\n}\n\nfunction spreadWeighted(owed: number, pool: ReadonlyArray<Pooled>): Map<number, number> {\n\tconst shares = new Map<number, number>();\n\tlet remaining = owed;\n\tlet active = [...pool];\n\n\twhile (active.length > 0 && Math.abs(remaining) > epsilon) {\n\t\tconst weights = sumOf(active.map(({ weight }) => weight));\n\t\tconst count = active.length;\n\t\tconst shareOf = (entry: Pooled): number =>\n\t\t\tremaining * (weights > 0 ? entry.weight / weights : 1 / count);\n\t\tconst full = active.filter((entry) => Math.abs(shareOf(entry)) >= entry.room);\n\n\t\tif (full.length === 0) {\n\t\t\tfor (const entry of active) shares.set(entry.index, shareOf(entry));\n\t\t\tbreak;\n\t\t}\n\n\t\tconst sign = Math.sign(remaining);\n\n\t\tfor (const entry of full) {\n\t\t\tshares.set(entry.index, sign * entry.room);\n\t\t\tremaining -= sign * entry.room;\n\t\t}\n\t\tactive = active.filter((entry) => !full.includes(entry));\n\t}\n\n\treturn shares;\n}\n\nfunction spreadInOrder(owed: number, pool: ReadonlyArray<Pooled>): Map<number, number> {\n\tconst shares = new Map<number, number>();\n\tlet remaining = owed;\n\n\tfor (const entry of pool) {\n\t\tif (Math.abs(remaining) <= epsilon) break;\n\n\t\tconst share = Math.sign(remaining) * Math.min(entry.room, Math.abs(remaining));\n\n\t\tshares.set(entry.index, share);\n\t\tremaining -= share;\n\t}\n\n\treturn shares;\n}\n\nfunction poolOrder(members: ReadonlyArray<SplitMember>, index: number): Array<number> {\n\tconst count = members.length;\n\tconst start = index + 1;\n\n\treturn Array.from({ length: count }, (_entry, offset) => (start + offset) % count).filter(\n\t\t(at) => at !== index && members[at]?.isFree === true,\n\t);\n}\n\nfunction stepWithin(member: SplitMember, from: number, owed: number): number | undefined {\n\tconst next = member.stepFrom(from, owed > 0 ? 1 : -1);\n\tconst change = trimFloat(next - from);\n\tconst isInside = next >= member.min && next <= member.max;\n\n\treturn change !== 0 && isInside && Math.abs(change) <= Math.abs(owed) + epsilon\n\t\t? next\n\t\t: undefined;\n}\n\nfunction stepToward(member: SplitMember, from: number, owed: number): number {\n\tlet value = from;\n\tlet remainder = owed;\n\n\twhile (Math.abs(remainder) > epsilon) {\n\t\tconst next = stepWithin(member, value, remainder);\n\t\tif (next === undefined) break;\n\n\t\tremainder = trimFloat(remainder - (next - value));\n\t\tvalue = next;\n\t}\n\n\treturn value;\n}\n\ninterface Settling {\n\texact: ReadonlyMap<number, number>;\n\torder: ReadonlyArray<number>;\n\ttotal: number;\n}\n\n// Furthest from its exact share first; a tie goes to the last in pool order\nfunction byShortfall(\n\tvalues: ReadonlyArray<number>,\n\t{ exact, order }: Settling,\n\tdirection: number,\n): Array<number> {\n\tconst shortfall = (index: number): number => {\n\t\tconst value = values[index] ?? NaN;\n\n\t\treturn trimFloat(direction * ((exact.get(index) ?? value) - value));\n\t};\n\n\treturn order.toReversed().sort((first, second) => shortfall(second) - shortfall(first));\n}\n\nfunction stepEach(\n\tmembers: ReadonlyArray<SplitMember>,\n\tvalues: Array<number>,\n\tpass: { neediest: ReadonlyArray<number>; owed: number },\n): number {\n\tlet remainder = pass.owed;\n\n\tfor (const index of pass.neediest) {\n\t\tconst member = members[index];\n\t\tconst value = values[index];\n\t\tconst next = member && value !== undefined ? stepWithin(member, value, remainder) : undefined;\n\t\tif (value === undefined || next === undefined) continue;\n\n\t\tvalues[index] = next;\n\t\tremainder = trimFloat(remainder - (next - value));\n\t}\n\n\treturn remainder;\n}\n\nfunction settle(\n\tmembers: ReadonlyArray<SplitMember>,\n\tvalues: Array<number>,\n\tsettling: Settling,\n): number {\n\tlet remainder = trimFloat(settling.total - sumOf(values));\n\tlet before = NaN;\n\n\twhile (remainder !== before && Math.abs(remainder) > epsilon) {\n\t\tbefore = remainder;\n\t\tremainder = stepEach(members, values, {\n\t\t\tneediest: byShortfall(values, settling, Math.sign(remainder)),\n\t\t\towed: remainder,\n\t\t});\n\t}\n\n\treturn remainder;\n}\n\nfunction pooled(\n\tmembers: ReadonlyArray<SplitMember>,\n\torder: ReadonlyArray<number>,\n\towed: number,\n): Array<Pooled> {\n\treturn order.flatMap((at): Array<Pooled> => {\n\t\tconst member = members[at];\n\t\tif (!member) return [];\n\n\t\tconst room = owed > 0 ? member.max - member.value : member.value - member.min;\n\n\t\treturn [{ index: at, room: Math.max(0, room), weight: member.value - member.min }];\n\t});\n}\n\nfunction shares(pool: Array<Pooled>, owed: number, mode: SplitMode): Map<number, number> {\n\tif (mode === 'cascade') return spreadInOrder(owed, pool);\n\tif (mode === 'equal')\n\t\treturn spreadWeighted(\n\t\t\towed,\n\t\t\tpool.map((entry) => ({ ...entry, weight: 1 })),\n\t\t);\n\n\treturn spreadWeighted(owed, pool);\n}\n\nfunction landMover(\n\tmembers: ReadonlyArray<SplitMember>,\n\tvalues: Array<number>,\n\tmove: SplitMove & { total: number },\n): number | undefined {\n\tconst { index, target, total } = move;\n\tconst mover = members[index];\n\tif (!mover?.isFree || !Number.isFinite(target)) return undefined;\n\n\tconst next = mover.snap(clamp(target, ...splitLimits(members, index, total)));\n\n\tvalues[index] = next;\n\n\treturn mover.value - next;\n}\n\nfunction landShares(\n\tmembers: ReadonlyArray<SplitMember>,\n\tvalues: Array<number>,\n\tspread: ReadonlyMap<number, number>,\n): Map<number, number> {\n\tconst exact = new Map<number, number>();\n\n\tfor (const [at, share] of spread) {\n\t\tconst member = members[at];\n\t\tif (!member) continue;\n\n\t\texact.set(at, member.value + share);\n\t\tvalues[at] = clamp(member.snap(member.value + share), member.min, member.max);\n\t}\n\n\treturn exact;\n}\n\ninterface Spread {\n\tleft: number;\n\torder: ReadonlyArray<number>;\n\tvalues: Array<number>;\n}\n\nfunction spread(\n\tmembers: ReadonlyArray<SplitMember>,\n\trule: SplitRule,\n\tmove?: SplitMove,\n): Spread | undefined {\n\tconst values = members.map(({ value }) => value);\n\tconst owed = move\n\t\t? landMover(members, values, { ...move, total: rule.total })\n\t\t: rule.total - sumOf(values);\n\tif (owed === undefined) return undefined;\n\n\tconst order = poolOrder(members, move?.index ?? -1);\n\tconst exact = landShares(members, values, shares(pooled(members, order, owed), owed, rule.mode));\n\n\treturn { left: settle(members, values, { exact, order, total: rule.total }), order, values };\n}\n\nfunction smallestStep(\n\tmembers: ReadonlyArray<SplitMember>,\n\t{ left, order, values }: Spread,\n): number | undefined {\n\tconst steps = order.flatMap((at) => {\n\t\tconst member = members[at];\n\t\tconst value = values[at];\n\t\tif (!member || value === undefined) return [];\n\n\t\tconst next = member.stepFrom(value, left > 0 ? 1 : -1);\n\t\tconst isInside = next >= member.min && next <= member.max;\n\n\t\treturn isInside && next !== value ? [trimFloat(next - value)] : [];\n\t});\n\n\treturn steps.toSorted((one, other) => Math.abs(one) - Math.abs(other))[0];\n}\n\n// The mover takes what is left on its own grid, or a sibling steps past the total and the mover gives the difference back\nfunction moverTargets(\n\tmembers: ReadonlyArray<SplitMember>,\n\tlanding: Spread,\n\tindex: number,\n): Array<number> {\n\tconst mover = members[index];\n\tconst landed = landing.values[index];\n\tif (!mover || landed === undefined) return [];\n\n\tconst step = smallestStep(members, landing);\n\tconst held = stepToward(mover, landed, landing.left);\n\n\treturn step === undefined\n\t\t? [held]\n\t\t: [held, stepToward(mover, landed, trimFloat(landing.left - step))];\n}\n\nfunction nearest(\n\tcandidates: Array<Array<number>>,\n\tmove: SplitMove,\n\tfrom: { landed: number; start: number },\n): Array<number> | undefined {\n\tconst { direction = 0, index } = move;\n\tconst moverOf = (candidate: Array<number>): number => candidate[index] ?? NaN;\n\tconst [first, ...rest] = candidates.toSorted(\n\t\t(one, other) => Math.abs(moverOf(one) - from.landed) - Math.abs(moverOf(other) - from.landed),\n\t);\n\tif (!first || direction === 0 || moverOf(first) !== from.start) return first;\n\n\treturn (\n\t\trest.find((candidate) => Math.sign(moverOf(candidate) - from.start) === direction) ?? first\n\t);\n}\n\nfunction close(\n\tmembers: ReadonlyArray<SplitMember>,\n\trule: SplitRule,\n\theld: { landing: Spread; move: SplitMove; mover: SplitMember },\n): Array<number> {\n\tconst { landing, move, mover } = held;\n\tconst { index } = move;\n\tconst landed = landing.values[index] ?? mover.value;\n\tconst targets = moverTargets(members, landing, index);\n\tconst closed = targets.flatMap((target) => {\n\t\tconst attempt = spread(members, rule, { index, target });\n\n\t\treturn attempt && Math.abs(attempt.left) <= epsilon ? [attempt.values] : [];\n\t});\n\n\treturn (\n\t\tnearest(closed, move, { landed, start: mover.value }) ??\n\t\tlanding.values.with(index, targets[0] ?? landed)\n\t);\n}\n\nexport function distribute(\n\tmembers: ReadonlyArray<SplitMember>,\n\trule: SplitRule,\n\tmove?: SplitMove,\n): Array<number> {\n\tconst landing = spread(members, rule, move);\n\tif (!landing) return members.map(({ value }) => value);\n\n\tconst mover = members[move?.index ?? -1];\n\tif (!move || !mover || Math.abs(landing.left) <= epsilon) return landing.values;\n\n\treturn close(members, rule, { landing, move, mover });\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { clampProportion, wrapProportion } from "./math.js";
|
|
2
|
+
|
|
3
|
+
//#region src/lib/drag-step.ts
|
|
4
|
+
const fineFactor = .1;
|
|
5
|
+
const mouseDragThresholdPx = 3;
|
|
6
|
+
const touchDragThresholdPx = 7;
|
|
7
|
+
function dragThresholdPx(pointerType) {
|
|
8
|
+
return pointerType === "touch" ? touchDragThresholdPx : mouseDragThresholdPx;
|
|
9
|
+
}
|
|
10
|
+
function isAcross(from, to, proportion) {
|
|
11
|
+
return from !== proportion && (from - proportion) * (to - proportion) <= 0;
|
|
12
|
+
}
|
|
13
|
+
function passDetent(hold, from, to) {
|
|
14
|
+
if (hold.slack === void 0 && !isAcross(from, to, hold.proportion)) return {
|
|
15
|
+
proportion: to,
|
|
16
|
+
slack: void 0
|
|
17
|
+
};
|
|
18
|
+
const slack = (hold.slack ?? 0) + to - hold.proportion;
|
|
19
|
+
if (Math.abs(slack) <= hold.zone) return {
|
|
20
|
+
proportion: void 0,
|
|
21
|
+
slack
|
|
22
|
+
};
|
|
23
|
+
return {
|
|
24
|
+
proportion: hold.proportion + slack - Math.sign(slack) * hold.zone,
|
|
25
|
+
slack: void 0
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
function nearestTurn(hold, from) {
|
|
29
|
+
return {
|
|
30
|
+
...hold,
|
|
31
|
+
proportion: hold.proportion + Math.round(from - hold.proportion)
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
function land(mapping, state, proportion) {
|
|
35
|
+
const rawProportion = mapping.isWrapping ? wrapProportion(proportion) : clampProportion(proportion);
|
|
36
|
+
return {
|
|
37
|
+
state: {
|
|
38
|
+
...state,
|
|
39
|
+
rawProportion
|
|
40
|
+
},
|
|
41
|
+
value: mapping.snap(mapping.valueAt(rawProportion))
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
function startDrag(mapping, start) {
|
|
45
|
+
const { detent: wanted, from, position, proportion, ...fixed } = start;
|
|
46
|
+
const state = {
|
|
47
|
+
...fixed,
|
|
48
|
+
isEngaged: false,
|
|
49
|
+
lastPosition: position,
|
|
50
|
+
rawProportion: proportion,
|
|
51
|
+
startPosition: position
|
|
52
|
+
};
|
|
53
|
+
if (!wanted) return state;
|
|
54
|
+
const value = mapping.snap(wanted.value);
|
|
55
|
+
const detent = {
|
|
56
|
+
proportion: mapping.proportionOf(value),
|
|
57
|
+
slack: value === from ? 0 : void 0,
|
|
58
|
+
value,
|
|
59
|
+
zone: wanted.zone
|
|
60
|
+
};
|
|
61
|
+
return {
|
|
62
|
+
...state,
|
|
63
|
+
detent
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
function stepDrag(mapping, state, move) {
|
|
67
|
+
if (!(state.isEngaged || move.isOutside || Math.abs(move.position - state.startPosition) >= state.thresholdPx)) return {
|
|
68
|
+
state,
|
|
69
|
+
value: void 0
|
|
70
|
+
};
|
|
71
|
+
const pace = move.isFine ? fineFactor : 1;
|
|
72
|
+
const to = state.rawProportion + (move.position - state.lastPosition) / state.travelPx * pace;
|
|
73
|
+
const moved = {
|
|
74
|
+
...state,
|
|
75
|
+
isEngaged: true,
|
|
76
|
+
lastPosition: move.position
|
|
77
|
+
};
|
|
78
|
+
const { detent } = state;
|
|
79
|
+
if (!detent) return land(mapping, moved, to);
|
|
80
|
+
const passed = passDetent(mapping.isWrapping ? nearestTurn(detent, state.rawProportion) : detent, state.rawProportion, to);
|
|
81
|
+
const held = {
|
|
82
|
+
...moved,
|
|
83
|
+
detent: {
|
|
84
|
+
...detent,
|
|
85
|
+
slack: passed.slack
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
if (passed.proportion === void 0) return {
|
|
89
|
+
state: {
|
|
90
|
+
...held,
|
|
91
|
+
rawProportion: detent.proportion
|
|
92
|
+
},
|
|
93
|
+
value: detent.value
|
|
94
|
+
};
|
|
95
|
+
return land(mapping, held, passed.proportion);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
//#endregion
|
|
99
|
+
export { dragThresholdPx, startDrag, stepDrag };
|
|
100
|
+
//# sourceMappingURL=drag-step.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"drag-step.js","names":[],"sources":["../../../src/lib/drag-step.ts"],"sourcesContent":["import type { ValueMapping } from '#lib/value-mapping.ts';\n\nimport { clampProportion, wrapProportion } from '#lib/math.ts';\n\ninterface DetentHold {\n\tproportion: number;\n\tslack: number | undefined;\n\tvalue: number;\n\tzone: number;\n}\n\nexport interface DragState {\n\tdetent?: DetentHold;\n\tisEngaged: boolean;\n\tlastPosition: number;\n\trawProportion: number;\n\tstartPosition: number;\n\tthresholdPx: number;\n\ttravelPx: number;\n}\n\nexport interface DragMove {\n\tisFine: boolean;\n\tisOutside: boolean;\n\tposition: number;\n}\n\ninterface DragStart {\n\tdetent?: { value: number; zone: number };\n\tfrom: number;\n\tposition: number;\n\tproportion: number;\n\tthresholdPx: number;\n\ttravelPx: number;\n}\n\ninterface DragStep {\n\tstate: DragState;\n\tvalue: number | undefined;\n}\n\nconst fineFactor = 0.1;\nconst mouseDragThresholdPx = 3;\nconst touchDragThresholdPx = 7;\n\nexport function dragThresholdPx(pointerType: string): number {\n\treturn pointerType === 'touch' ? touchDragThresholdPx : mouseDragThresholdPx;\n}\n\nfunction isAcross(from: number, to: number, proportion: number): boolean {\n\treturn from !== proportion && (from - proportion) * (to - proportion) <= 0;\n}\n\nfunction passDetent(\n\thold: DetentHold,\n\tfrom: number,\n\tto: number,\n): { proportion: number | undefined; slack: number | undefined } {\n\tif (hold.slack === undefined && !isAcross(from, to, hold.proportion)) {\n\t\treturn { proportion: to, slack: undefined };\n\t}\n\n\tconst slack = (hold.slack ?? 0) + to - hold.proportion;\n\n\tif (Math.abs(slack) <= hold.zone) return { proportion: undefined, slack };\n\n\treturn { proportion: hold.proportion + slack - Math.sign(slack) * hold.zone, slack: undefined };\n}\n\nfunction nearestTurn(hold: DetentHold, from: number): DetentHold {\n\treturn { ...hold, proportion: hold.proportion + Math.round(from - hold.proportion) };\n}\n\nfunction land(mapping: ValueMapping, state: DragState, proportion: number): DragStep {\n\tconst rawProportion = mapping.isWrapping\n\t\t? wrapProportion(proportion)\n\t\t: clampProportion(proportion);\n\n\treturn {\n\t\tstate: { ...state, rawProportion },\n\t\tvalue: mapping.snap(mapping.valueAt(rawProportion)),\n\t};\n}\n\nexport function startDrag(mapping: ValueMapping, start: DragStart): DragState {\n\tconst { detent: wanted, from, position, proportion, ...fixed } = start;\n\tconst state = {\n\t\t...fixed,\n\t\tisEngaged: false,\n\t\tlastPosition: position,\n\t\trawProportion: proportion,\n\t\tstartPosition: position,\n\t};\n\tif (!wanted) return state;\n\n\tconst value = mapping.snap(wanted.value);\n\tconst detent = {\n\t\tproportion: mapping.proportionOf(value),\n\t\tslack: value === from ? 0 : undefined,\n\t\tvalue,\n\t\tzone: wanted.zone,\n\t};\n\n\treturn { ...state, detent };\n}\n\nexport function stepDrag(mapping: ValueMapping, state: DragState, move: DragMove): DragStep {\n\tconst isEngaged =\n\t\tstate.isEngaged ||\n\t\tmove.isOutside ||\n\t\tMath.abs(move.position - state.startPosition) >= state.thresholdPx;\n\n\tif (!isEngaged) return { state, value: undefined };\n\n\tconst pace = move.isFine ? fineFactor : 1;\n\tconst to = state.rawProportion + ((move.position - state.lastPosition) / state.travelPx) * pace;\n\tconst moved = { ...state, isEngaged: true, lastPosition: move.position };\n\tconst { detent } = state;\n\tif (!detent) return land(mapping, moved, to);\n\n\tconst near = mapping.isWrapping ? nearestTurn(detent, state.rawProportion) : detent;\n\tconst passed = passDetent(near, state.rawProportion, to);\n\tconst held = { ...moved, detent: { ...detent, slack: passed.slack } };\n\tif (passed.proportion === undefined) {\n\t\treturn { state: { ...held, rawProportion: detent.proportion }, value: detent.value };\n\t}\n\n\treturn land(mapping, held, passed.proportion);\n}\n"],"mappings":";;;AAyCA,MAAM,aAAa;AACnB,MAAM,uBAAuB;AAC7B,MAAM,uBAAuB;AAE7B,SAAgB,gBAAgB,aAA6B;CAC5D,OAAO,gBAAgB,UAAU,uBAAuB;AACzD;AAEA,SAAS,SAAS,MAAc,IAAY,YAA6B;CACxE,OAAO,SAAS,eAAe,OAAO,eAAe,KAAK,eAAe;AAC1E;AAEA,SAAS,WACR,MACA,MACA,IACgE;CAChE,IAAI,KAAK,UAAU,UAAa,CAAC,SAAS,MAAM,IAAI,KAAK,UAAU,GAClE,OAAO;EAAE,YAAY;EAAI,OAAO;CAAU;CAG3C,MAAM,SAAS,KAAK,SAAS,KAAK,KAAK,KAAK;CAE5C,IAAI,KAAK,IAAI,KAAK,KAAK,KAAK,MAAM,OAAO;EAAE,YAAY;EAAW;CAAM;CAExE,OAAO;EAAE,YAAY,KAAK,aAAa,QAAQ,KAAK,KAAK,KAAK,IAAI,KAAK;EAAM,OAAO;CAAU;AAC/F;AAEA,SAAS,YAAY,MAAkB,MAA0B;CAChE,OAAO;EAAE,GAAG;EAAM,YAAY,KAAK,aAAa,KAAK,MAAM,OAAO,KAAK,UAAU;CAAE;AACpF;AAEA,SAAS,KAAK,SAAuB,OAAkB,YAA8B;CACpF,MAAM,gBAAgB,QAAQ,aAC3B,eAAe,UAAU,IACzB,gBAAgB,UAAU;CAE7B,OAAO;EACN,OAAO;GAAE,GAAG;GAAO;EAAc;EACjC,OAAO,QAAQ,KAAK,QAAQ,QAAQ,aAAa,CAAC;CACnD;AACD;AAEA,SAAgB,UAAU,SAAuB,OAA6B;CAC7E,MAAM,EAAE,QAAQ,QAAQ,MAAM,UAAU,YAAY,GAAG,UAAU;CACjE,MAAM,QAAQ;EACb,GAAG;EACH,WAAW;EACX,cAAc;EACd,eAAe;EACf,eAAe;CAChB;CACA,IAAI,CAAC,QAAQ,OAAO;CAEpB,MAAM,QAAQ,QAAQ,KAAK,OAAO,KAAK;CACvC,MAAM,SAAS;EACd,YAAY,QAAQ,aAAa,KAAK;EACtC,OAAO,UAAU,OAAO,IAAI;EAC5B;EACA,MAAM,OAAO;CACd;CAEA,OAAO;EAAE,GAAG;EAAO;CAAO;AAC3B;AAEA,SAAgB,SAAS,SAAuB,OAAkB,MAA0B;CAM3F,IAAI,EAJH,MAAM,aACN,KAAK,aACL,KAAK,IAAI,KAAK,WAAW,MAAM,aAAa,KAAK,MAAM,cAExC,OAAO;EAAE;EAAO,OAAO;CAAU;CAEjD,MAAM,OAAO,KAAK,SAAS,aAAa;CACxC,MAAM,KAAK,MAAM,iBAAkB,KAAK,WAAW,MAAM,gBAAgB,MAAM,WAAY;CAC3F,MAAM,QAAQ;EAAE,GAAG;EAAO,WAAW;EAAM,cAAc,KAAK;CAAS;CACvE,MAAM,EAAE,WAAW;CACnB,IAAI,CAAC,QAAQ,OAAO,KAAK,SAAS,OAAO,EAAE;CAG3C,MAAM,SAAS,WADF,QAAQ,aAAa,YAAY,QAAQ,MAAM,aAAa,IAAI,QAC7C,MAAM,eAAe,EAAE;CACvD,MAAM,OAAO;EAAE,GAAG;EAAO,QAAQ;GAAE,GAAG;GAAQ,OAAO,OAAO;EAAM;CAAE;CACpE,IAAI,OAAO,eAAe,QACzB,OAAO;EAAE,OAAO;GAAE,GAAG;GAAM,eAAe,OAAO;EAAW;EAAG,OAAO,OAAO;CAAM;CAGpF,OAAO,KAAK,SAAS,MAAM,OAAO,UAAU;AAC7C"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
//#region src/lib/envelope-curve.ts
|
|
2
|
+
const flatCurve = .001;
|
|
3
|
+
function envelopeCurve(position, curve) {
|
|
4
|
+
if (Math.abs(curve) < flatCurve) return position;
|
|
5
|
+
return Math.expm1(curve * position) / Math.expm1(curve);
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
//#endregion
|
|
9
|
+
export { envelopeCurve };
|
|
10
|
+
//# sourceMappingURL=envelope-curve.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"envelope-curve.js","names":[],"sources":["../../../src/lib/envelope-curve.ts"],"sourcesContent":["const flatCurve = 0.001;\n\nexport function envelopeCurve(position: number, curve: number): number {\n\tif (Math.abs(curve) < flatCurve) return position;\n\n\treturn Math.expm1(curve * position) / Math.expm1(curve);\n}\n"],"mappings":";AAAA,MAAM,YAAY;AAElB,SAAgB,cAAc,UAAkB,OAAuB;CACtE,IAAI,KAAK,IAAI,KAAK,IAAI,WAAW,OAAO;CAExC,OAAO,KAAK,MAAM,QAAQ,QAAQ,IAAI,KAAK,MAAM,KAAK;AACvD"}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { startDrag, stepDrag } from "./drag-step.js";
|
|
2
|
+
|
|
3
|
+
//#region src/lib/field.ts
|
|
4
|
+
function startAxis(start, position) {
|
|
5
|
+
if (!start) return void 0;
|
|
6
|
+
const { mapping, ...rest } = start;
|
|
7
|
+
return startDrag(mapping, {
|
|
8
|
+
...rest,
|
|
9
|
+
position,
|
|
10
|
+
thresholdPx: 0
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
function startFieldDrag(start) {
|
|
14
|
+
const { position, thresholdPx } = start;
|
|
15
|
+
return {
|
|
16
|
+
isEngaged: false,
|
|
17
|
+
lock: void 0,
|
|
18
|
+
startPosition: position,
|
|
19
|
+
thresholdPx,
|
|
20
|
+
x: startAxis(start.x, position.x),
|
|
21
|
+
y: startAxis(start.y, position.y)
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
function dominantAxis(state, move) {
|
|
25
|
+
if (!state.x) return "y";
|
|
26
|
+
if (!state.y) return "x";
|
|
27
|
+
const { startPosition } = state;
|
|
28
|
+
return Math.abs(move.x - startPosition.x) >= Math.abs(move.y - startPosition.y) ? "x" : "y";
|
|
29
|
+
}
|
|
30
|
+
function stepAxis(mapping, drag, move) {
|
|
31
|
+
if (!mapping || !drag) return {
|
|
32
|
+
state: drag,
|
|
33
|
+
value: void 0
|
|
34
|
+
};
|
|
35
|
+
if (move.isHeld) return {
|
|
36
|
+
state: {
|
|
37
|
+
...drag,
|
|
38
|
+
lastPosition: move.position
|
|
39
|
+
},
|
|
40
|
+
value: void 0
|
|
41
|
+
};
|
|
42
|
+
return stepDrag(mapping, drag, {
|
|
43
|
+
isFine: move.isFine,
|
|
44
|
+
isOutside: false,
|
|
45
|
+
position: move.position
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
function stepFieldDrag(mappings, state, move) {
|
|
49
|
+
const { startPosition } = state;
|
|
50
|
+
if (!(state.isEngaged || Math.hypot(move.x - startPosition.x, move.y - startPosition.y) >= state.thresholdPx)) return {
|
|
51
|
+
state,
|
|
52
|
+
x: void 0,
|
|
53
|
+
y: void 0
|
|
54
|
+
};
|
|
55
|
+
const lock = move.isLocked ? state.lock ?? dominantAxis(state, move) : void 0;
|
|
56
|
+
const { isFine } = move;
|
|
57
|
+
const x = stepAxis(mappings.x, state.x, {
|
|
58
|
+
isFine,
|
|
59
|
+
isHeld: lock === "y",
|
|
60
|
+
position: move.x
|
|
61
|
+
});
|
|
62
|
+
const y = stepAxis(mappings.y, state.y, {
|
|
63
|
+
isFine,
|
|
64
|
+
isHeld: lock === "x",
|
|
65
|
+
position: move.y
|
|
66
|
+
});
|
|
67
|
+
return {
|
|
68
|
+
state: {
|
|
69
|
+
...state,
|
|
70
|
+
isEngaged: true,
|
|
71
|
+
lock,
|
|
72
|
+
x: x.state,
|
|
73
|
+
y: y.state
|
|
74
|
+
},
|
|
75
|
+
x: x.value,
|
|
76
|
+
y: y.value
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
function pointerPosition(event) {
|
|
80
|
+
return {
|
|
81
|
+
x: event.clientX,
|
|
82
|
+
y: -event.clientY
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
function pointerMove(event) {
|
|
86
|
+
return {
|
|
87
|
+
...pointerPosition(event),
|
|
88
|
+
isFine: event.shiftKey,
|
|
89
|
+
isLocked: event.altKey
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
//#endregion
|
|
94
|
+
export { pointerMove, pointerPosition, startFieldDrag, stepFieldDrag };
|
|
95
|
+
//# sourceMappingURL=field.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"field.js","names":[],"sources":["../../../src/lib/field.ts"],"sourcesContent":["import type { DragState } from '#lib/drag-step.ts';\nimport type { ValueMapping } from '#lib/value-mapping.ts';\n\nimport { startDrag, stepDrag } from '#lib/drag-step.ts';\n\nexport type FieldAxis = 'x' | 'y';\n\nexport interface FieldPoint {\n\tx: number;\n\ty: number;\n}\n\ninterface FieldAxisStart {\n\tfrom: number;\n\tmapping: ValueMapping;\n\tproportion: number;\n\ttravelPx: number;\n}\n\ninterface FieldStart {\n\tposition: FieldPoint;\n\tthresholdPx: number;\n\tx?: FieldAxisStart | undefined;\n\ty?: FieldAxisStart | undefined;\n}\n\nexport interface FieldDragState {\n\tisEngaged: boolean;\n\tlock: FieldAxis | undefined;\n\tstartPosition: FieldPoint;\n\tthresholdPx: number;\n\tx: DragState | undefined;\n\ty: DragState | undefined;\n}\n\nexport interface FieldMove extends FieldPoint {\n\tisFine: boolean;\n\tisLocked: boolean;\n}\n\nexport interface FieldMappings {\n\tx?: undefined | ValueMapping;\n\ty?: undefined | ValueMapping;\n}\n\ninterface FieldStep {\n\tstate: FieldDragState;\n\tx: number | undefined;\n\ty: number | undefined;\n}\n\ninterface AxisMove {\n\tisFine: boolean;\n\tisHeld: boolean;\n\tposition: number;\n}\n\nfunction startAxis(start: FieldAxisStart | undefined, position: number): DragState | undefined {\n\tif (!start) return undefined;\n\n\tconst { mapping, ...rest } = start;\n\n\treturn startDrag(mapping, { ...rest, position, thresholdPx: 0 });\n}\n\nexport function startFieldDrag(start: FieldStart): FieldDragState {\n\tconst { position, thresholdPx } = start;\n\n\treturn {\n\t\tisEngaged: false,\n\t\tlock: undefined,\n\t\tstartPosition: position,\n\t\tthresholdPx,\n\t\tx: startAxis(start.x, position.x),\n\t\ty: startAxis(start.y, position.y),\n\t};\n}\n\nfunction dominantAxis(state: FieldDragState, move: FieldPoint): FieldAxis {\n\tif (!state.x) return 'y';\n\tif (!state.y) return 'x';\n\n\tconst { startPosition } = state;\n\n\treturn Math.abs(move.x - startPosition.x) >= Math.abs(move.y - startPosition.y) ? 'x' : 'y';\n}\n\nfunction stepAxis(\n\tmapping: undefined | ValueMapping,\n\tdrag: DragState | undefined,\n\tmove: AxisMove,\n): { state: DragState | undefined; value: number | undefined } {\n\tif (!mapping || !drag) return { state: drag, value: undefined };\n\tif (move.isHeld) return { state: { ...drag, lastPosition: move.position }, value: undefined };\n\n\treturn stepDrag(mapping, drag, {\n\t\tisFine: move.isFine,\n\t\tisOutside: false,\n\t\tposition: move.position,\n\t});\n}\n\nexport function stepFieldDrag(\n\tmappings: FieldMappings,\n\tstate: FieldDragState,\n\tmove: FieldMove,\n): FieldStep {\n\tconst { startPosition } = state;\n\tconst isEngaged =\n\t\tstate.isEngaged ||\n\t\tMath.hypot(move.x - startPosition.x, move.y - startPosition.y) >= state.thresholdPx;\n\n\tif (!isEngaged) return { state, x: undefined, y: undefined };\n\n\tconst lock = move.isLocked ? (state.lock ?? dominantAxis(state, move)) : undefined;\n\tconst { isFine } = move;\n\tconst x = stepAxis(mappings.x, state.x, { isFine, isHeld: lock === 'y', position: move.x });\n\tconst y = stepAxis(mappings.y, state.y, { isFine, isHeld: lock === 'x', position: move.y });\n\n\treturn {\n\t\tstate: { ...state, isEngaged: true, lock, x: x.state, y: y.state },\n\t\tx: x.value,\n\t\ty: y.value,\n\t};\n}\n\nexport function pointerPosition(event: PointerEvent): FieldPoint {\n\treturn { x: event.clientX, y: -event.clientY };\n}\n\nexport function pointerMove(event: PointerEvent): FieldMove {\n\treturn { ...pointerPosition(event), isFine: event.shiftKey, isLocked: event.altKey };\n}\n"],"mappings":";;;AAyDA,SAAS,UAAU,OAAmC,UAAyC;CAC9F,IAAI,CAAC,OAAO,OAAO;CAEnB,MAAM,EAAE,SAAS,GAAG,SAAS;CAE7B,OAAO,UAAU,SAAS;EAAE,GAAG;EAAM;EAAU,aAAa;CAAE,CAAC;AAChE;AAEA,SAAgB,eAAe,OAAmC;CACjE,MAAM,EAAE,UAAU,gBAAgB;CAElC,OAAO;EACN,WAAW;EACX,MAAM;EACN,eAAe;EACf;EACA,GAAG,UAAU,MAAM,GAAG,SAAS,CAAC;EAChC,GAAG,UAAU,MAAM,GAAG,SAAS,CAAC;CACjC;AACD;AAEA,SAAS,aAAa,OAAuB,MAA6B;CACzE,IAAI,CAAC,MAAM,GAAG,OAAO;CACrB,IAAI,CAAC,MAAM,GAAG,OAAO;CAErB,MAAM,EAAE,kBAAkB;CAE1B,OAAO,KAAK,IAAI,KAAK,IAAI,cAAc,CAAC,KAAK,KAAK,IAAI,KAAK,IAAI,cAAc,CAAC,IAAI,MAAM;AACzF;AAEA,SAAS,SACR,SACA,MACA,MAC8D;CAC9D,IAAI,CAAC,WAAW,CAAC,MAAM,OAAO;EAAE,OAAO;EAAM,OAAO;CAAU;CAC9D,IAAI,KAAK,QAAQ,OAAO;EAAE,OAAO;GAAE,GAAG;GAAM,cAAc,KAAK;EAAS;EAAG,OAAO;CAAU;CAE5F,OAAO,SAAS,SAAS,MAAM;EAC9B,QAAQ,KAAK;EACb,WAAW;EACX,UAAU,KAAK;CAChB,CAAC;AACF;AAEA,SAAgB,cACf,UACA,OACA,MACY;CACZ,MAAM,EAAE,kBAAkB;CAK1B,IAAI,EAHH,MAAM,aACN,KAAK,MAAM,KAAK,IAAI,cAAc,GAAG,KAAK,IAAI,cAAc,CAAC,KAAK,MAAM,cAEzD,OAAO;EAAE;EAAO,GAAG;EAAW,GAAG;CAAU;CAE3D,MAAM,OAAO,KAAK,WAAY,MAAM,QAAQ,aAAa,OAAO,IAAI,IAAK;CACzE,MAAM,EAAE,WAAW;CACnB,MAAM,IAAI,SAAS,SAAS,GAAG,MAAM,GAAG;EAAE;EAAQ,QAAQ,SAAS;EAAK,UAAU,KAAK;CAAE,CAAC;CAC1F,MAAM,IAAI,SAAS,SAAS,GAAG,MAAM,GAAG;EAAE;EAAQ,QAAQ,SAAS;EAAK,UAAU,KAAK;CAAE,CAAC;CAE1F,OAAO;EACN,OAAO;GAAE,GAAG;GAAO,WAAW;GAAM;GAAM,GAAG,EAAE;GAAO,GAAG,EAAE;EAAM;EACjE,GAAG,EAAE;EACL,GAAG,EAAE;CACN;AACD;AAEA,SAAgB,gBAAgB,OAAiC;CAChE,OAAO;EAAE,GAAG,MAAM;EAAS,GAAG,CAAC,MAAM;CAAQ;AAC9C;AAEA,SAAgB,YAAY,OAAgC;CAC3D,OAAO;EAAE,GAAG,gBAAgB,KAAK;EAAG,QAAQ,MAAM;EAAU,UAAU,MAAM;CAAO;AACpF"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
//#region src/lib/focus-by-pointer.ts
|
|
2
|
+
const modifierKeys = /* @__PURE__ */ new Set([
|
|
3
|
+
"Alt",
|
|
4
|
+
"Control",
|
|
5
|
+
"Meta",
|
|
6
|
+
"Shift"
|
|
7
|
+
]);
|
|
8
|
+
function focusByPointer(target) {
|
|
9
|
+
const isMarked = "sonicPointerFocus" in target.dataset;
|
|
10
|
+
target.dataset.sonicPointerFocus = "";
|
|
11
|
+
target.focus();
|
|
12
|
+
if (isMarked) return;
|
|
13
|
+
const marking = new AbortController();
|
|
14
|
+
const clear = () => {
|
|
15
|
+
delete target.dataset.sonicPointerFocus;
|
|
16
|
+
marking.abort();
|
|
17
|
+
};
|
|
18
|
+
target.addEventListener("blur", () => {
|
|
19
|
+
if (target.ownerDocument.activeElement !== target) clear();
|
|
20
|
+
}, { signal: marking.signal });
|
|
21
|
+
target.addEventListener("keydown", (event) => {
|
|
22
|
+
if (event.metaKey || event.ctrlKey || event.altKey || modifierKeys.has(event.key)) return;
|
|
23
|
+
clear();
|
|
24
|
+
}, { signal: marking.signal });
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
//#endregion
|
|
28
|
+
export { focusByPointer };
|
|
29
|
+
//# sourceMappingURL=focus-by-pointer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"focus-by-pointer.js","names":[],"sources":["../../../src/lib/focus-by-pointer.ts"],"sourcesContent":["const modifierKeys = new Set(['Alt', 'Control', 'Meta', 'Shift']);\n\n// A script focus during a mouse press still matches `:focus-visible`\nexport function focusByPointer(target: HTMLElement): void {\n\tconst isMarked = 'sonicPointerFocus' in target.dataset;\n\n\ttarget.dataset.sonicPointerFocus = '';\n\ttarget.focus();\n\tif (isMarked) return;\n\n\tconst marking = new AbortController();\n\tconst clear = (): void => {\n\t\tdelete target.dataset.sonicPointerFocus;\n\t\tmarking.abort();\n\t};\n\n\ttarget.addEventListener(\n\t\t'blur',\n\t\t() => {\n\t\t\t// A window or tab switch blurs the target and leaves it the active element\n\t\t\tif (target.ownerDocument.activeElement !== target) clear();\n\t\t},\n\t\t{ signal: marking.signal },\n\t);\n\ttarget.addEventListener(\n\t\t'keydown',\n\t\t(event) => {\n\t\t\tif (event.metaKey || event.ctrlKey || event.altKey || modifierKeys.has(event.key)) return;\n\n\t\t\tclear();\n\t\t},\n\t\t{ signal: marking.signal },\n\t);\n}\n"],"mappings":";AAAA,MAAM,+BAAe,IAAI,IAAI;CAAC;CAAO;CAAW;CAAQ;AAAO,CAAC;AAGhE,SAAgB,eAAe,QAA2B;CACzD,MAAM,WAAW,uBAAuB,OAAO;CAE/C,OAAO,QAAQ,oBAAoB;CACnC,OAAO,MAAM;CACb,IAAI,UAAU;CAEd,MAAM,UAAU,IAAI,gBAAgB;CACpC,MAAM,cAAoB;EACzB,OAAO,OAAO,QAAQ;EACtB,QAAQ,MAAM;CACf;CAEA,OAAO,iBACN,cACM;EAEL,IAAI,OAAO,cAAc,kBAAkB,QAAQ,MAAM;CAC1D,GACA,EAAE,QAAQ,QAAQ,OAAO,CAC1B;CACA,OAAO,iBACN,YACC,UAAU;EACV,IAAI,MAAM,WAAW,MAAM,WAAW,MAAM,UAAU,aAAa,IAAI,MAAM,GAAG,GAAG;EAEnF,MAAM;CACP,GACA,EAAE,QAAQ,QAAQ,OAAO,CAC1B;AACD"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { trimFloat } from "./math.js";
|
|
2
|
+
|
|
3
|
+
//#region src/lib/frame-timeline.ts
|
|
4
|
+
const placeholderPeriodMs = 2500;
|
|
5
|
+
function windowStart(input, seconds, windowSeconds) {
|
|
6
|
+
if (!input.isPaged) return seconds - windowSeconds / 2;
|
|
7
|
+
if (input.held) return input.held.grabbed.startSeconds + seconds - input.held.grabbed.seconds;
|
|
8
|
+
return Math.floor(trimFloat(seconds / windowSeconds)) * windowSeconds;
|
|
9
|
+
}
|
|
10
|
+
function overlap([from, to], [low, high]) {
|
|
11
|
+
const start = Math.max(from, low);
|
|
12
|
+
const end = Math.min(to, high);
|
|
13
|
+
return end > start ? [start, end] : void 0;
|
|
14
|
+
}
|
|
15
|
+
function pendingIn({ pending, range }, window) {
|
|
16
|
+
return pending.flatMap((region) => {
|
|
17
|
+
const inRange = overlap(region, range);
|
|
18
|
+
const shown = inRange && overlap(inRange, window);
|
|
19
|
+
return shown ? [shown] : [];
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
function placeholderPhase({ frameMs, isPaged }, pending) {
|
|
23
|
+
return !isPaged && pending.length > 0 ? frameMs / placeholderPeriodMs : void 0;
|
|
24
|
+
}
|
|
25
|
+
function frameTimeline(input) {
|
|
26
|
+
const { clockSeconds, held, range, size } = input;
|
|
27
|
+
const seconds = held?.seconds ?? clockSeconds;
|
|
28
|
+
const pixelsPerSecond = input.zoom * size.dpr;
|
|
29
|
+
const windowSeconds = size.width / pixelsPerSecond;
|
|
30
|
+
const startSeconds = windowStart(input, seconds, windowSeconds);
|
|
31
|
+
const pending = pendingIn(input, [startSeconds, startSeconds + windowSeconds]);
|
|
32
|
+
const placeholder = placeholderPhase(input, pending);
|
|
33
|
+
const phase = placeholder ?? 0;
|
|
34
|
+
const wanted = overlap([startSeconds - windowSeconds, startSeconds + 2 * windowSeconds], range);
|
|
35
|
+
const at = (time) => (time - startSeconds) / windowSeconds;
|
|
36
|
+
return {
|
|
37
|
+
...held ? { ghostAt: at(clockSeconds) } : {},
|
|
38
|
+
isMoving: held !== void 0 || placeholder !== void 0 || input.isPlaying,
|
|
39
|
+
paintKey: [
|
|
40
|
+
startSeconds,
|
|
41
|
+
windowSeconds,
|
|
42
|
+
...range,
|
|
43
|
+
phase,
|
|
44
|
+
pending.join(",")
|
|
45
|
+
].join(":"),
|
|
46
|
+
playheadAt: at(seconds),
|
|
47
|
+
seconds,
|
|
48
|
+
view: {
|
|
49
|
+
dpr: size.dpr,
|
|
50
|
+
height: size.height,
|
|
51
|
+
pending,
|
|
52
|
+
phase,
|
|
53
|
+
pixelsPerSecond,
|
|
54
|
+
startSeconds,
|
|
55
|
+
width: size.width
|
|
56
|
+
},
|
|
57
|
+
...wanted ? { wanted } : {}
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
//#endregion
|
|
62
|
+
export { frameTimeline };
|
|
63
|
+
//# sourceMappingURL=frame-timeline.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"frame-timeline.js","names":[],"sources":["../../../src/lib/frame-timeline.ts"],"sourcesContent":["import type { SurfaceSize } from '#lib/canvas-surface.ts';\n\nimport { trimFloat } from '#lib/math.ts';\n\nexport interface View {\n\tdpr: number;\n\theight: number;\n\tpending: Array<[number, number]>;\n\tphase: number;\n\tpixelsPerSecond: number;\n\tstartSeconds: number;\n\twidth: number;\n}\n\nexport interface Drawn {\n\tseconds: number;\n\tstartSeconds: number;\n}\n\ninterface TimelineInput {\n\tclockSeconds: number;\n\tframeMs: number;\n\theld?: { grabbed: Drawn; seconds: number };\n\tisPaged: boolean;\n\tisPlaying: boolean;\n\tpending: ReadonlyArray<[number, number]>;\n\trange: [number, number];\n\tsize: SurfaceSize;\n\tzoom: number;\n}\n\ninterface Timeline {\n\tghostAt?: number;\n\tisMoving: boolean;\n\tpaintKey: string;\n\tplayheadAt: number;\n\tseconds: number;\n\tview: View;\n\twanted?: [number, number];\n}\n\nconst placeholderPeriodMs = 2500;\n\nfunction windowStart(input: TimelineInput, seconds: number, windowSeconds: number): number {\n\tif (!input.isPaged) return seconds - windowSeconds / 2;\n\tif (input.held) return input.held.grabbed.startSeconds + seconds - input.held.grabbed.seconds;\n\n\t// 0.3 / 0.1 is 2.9999999999999996, a page short\n\treturn Math.floor(trimFloat(seconds / windowSeconds)) * windowSeconds;\n}\n\nfunction overlap(\n\t[from, to]: readonly [number, number],\n\t[low, high]: readonly [number, number],\n): [number, number] | undefined {\n\tconst start = Math.max(from, low);\n\tconst end = Math.min(to, high);\n\n\treturn end > start ? [start, end] : undefined;\n}\n\nfunction pendingIn(\n\t{ pending, range }: TimelineInput,\n\twindow: [number, number],\n): Array<[number, number]> {\n\treturn pending.flatMap((region) => {\n\t\tconst inRange = overlap(region, range);\n\t\tconst shown = inRange && overlap(inRange, window);\n\n\t\treturn shown ? [shown] : [];\n\t});\n}\n\nfunction placeholderPhase(\n\t{ frameMs, isPaged }: TimelineInput,\n\tpending: ReadonlyArray<unknown>,\n): number | undefined {\n\treturn !isPaged && pending.length > 0 ? frameMs / placeholderPeriodMs : undefined;\n}\n\nexport function frameTimeline(input: TimelineInput): Timeline {\n\tconst { clockSeconds, held, range, size } = input;\n\tconst seconds = held?.seconds ?? clockSeconds;\n\tconst pixelsPerSecond = input.zoom * size.dpr;\n\tconst windowSeconds = size.width / pixelsPerSecond;\n\tconst startSeconds = windowStart(input, seconds, windowSeconds);\n\tconst pending = pendingIn(input, [startSeconds, startSeconds + windowSeconds]);\n\tconst placeholder = placeholderPhase(input, pending);\n\tconst phase = placeholder ?? 0;\n\tconst wanted = overlap([startSeconds - windowSeconds, startSeconds + 2 * windowSeconds], range);\n\tconst at = (time: number): number => (time - startSeconds) / windowSeconds;\n\n\treturn {\n\t\t...(held ? { ghostAt: at(clockSeconds) } : {}),\n\t\tisMoving: held !== undefined || placeholder !== undefined || input.isPlaying,\n\t\tpaintKey: [startSeconds, windowSeconds, ...range, phase, pending.join(',')].join(':'),\n\t\tplayheadAt: at(seconds),\n\t\tseconds,\n\t\tview: {\n\t\t\tdpr: size.dpr,\n\t\t\theight: size.height,\n\t\t\tpending,\n\t\t\tphase,\n\t\t\tpixelsPerSecond,\n\t\t\tstartSeconds,\n\t\t\twidth: size.width,\n\t\t},\n\t\t...(wanted ? { wanted } : {}),\n\t};\n}\n"],"mappings":";;;AAyCA,MAAM,sBAAsB;AAE5B,SAAS,YAAY,OAAsB,SAAiB,eAA+B;CAC1F,IAAI,CAAC,MAAM,SAAS,OAAO,UAAU,gBAAgB;CACrD,IAAI,MAAM,MAAM,OAAO,MAAM,KAAK,QAAQ,eAAe,UAAU,MAAM,KAAK,QAAQ;CAGtF,OAAO,KAAK,MAAM,UAAU,UAAU,aAAa,CAAC,IAAI;AACzD;AAEA,SAAS,QACR,CAAC,MAAM,KACP,CAAC,KAAK,OACyB;CAC/B,MAAM,QAAQ,KAAK,IAAI,MAAM,GAAG;CAChC,MAAM,MAAM,KAAK,IAAI,IAAI,IAAI;CAE7B,OAAO,MAAM,QAAQ,CAAC,OAAO,GAAG,IAAI;AACrC;AAEA,SAAS,UACR,EAAE,SAAS,SACX,QAC0B;CAC1B,OAAO,QAAQ,SAAS,WAAW;EAClC,MAAM,UAAU,QAAQ,QAAQ,KAAK;EACrC,MAAM,QAAQ,WAAW,QAAQ,SAAS,MAAM;EAEhD,OAAO,QAAQ,CAAC,KAAK,IAAI,CAAC;CAC3B,CAAC;AACF;AAEA,SAAS,iBACR,EAAE,SAAS,WACX,SACqB;CACrB,OAAO,CAAC,WAAW,QAAQ,SAAS,IAAI,UAAU,sBAAsB;AACzE;AAEA,SAAgB,cAAc,OAAgC;CAC7D,MAAM,EAAE,cAAc,MAAM,OAAO,SAAS;CAC5C,MAAM,UAAU,MAAM,WAAW;CACjC,MAAM,kBAAkB,MAAM,OAAO,KAAK;CAC1C,MAAM,gBAAgB,KAAK,QAAQ;CACnC,MAAM,eAAe,YAAY,OAAO,SAAS,aAAa;CAC9D,MAAM,UAAU,UAAU,OAAO,CAAC,cAAc,eAAe,aAAa,CAAC;CAC7E,MAAM,cAAc,iBAAiB,OAAO,OAAO;CACnD,MAAM,QAAQ,eAAe;CAC7B,MAAM,SAAS,QAAQ,CAAC,eAAe,eAAe,eAAe,IAAI,aAAa,GAAG,KAAK;CAC9F,MAAM,MAAM,UAA0B,OAAO,gBAAgB;CAE7D,OAAO;EACN,GAAI,OAAO,EAAE,SAAS,GAAG,YAAY,EAAE,IAAI,CAAC;EAC5C,UAAU,SAAS,UAAa,gBAAgB,UAAa,MAAM;EACnE,UAAU;GAAC;GAAc;GAAe,GAAG;GAAO;GAAO,QAAQ,KAAK,GAAG;EAAC,CAAC,CAAC,KAAK,GAAG;EACpF,YAAY,GAAG,OAAO;EACtB;EACA,MAAM;GACL,KAAK,KAAK;GACV,QAAQ,KAAK;GACb;GACA;GACA;GACA;GACA,OAAO,KAAK;EACb;EACA,GAAI,SAAS,EAAE,OAAO,IAAI,CAAC;CAC5B;AACD"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
//#region src/lib/hold.ts
|
|
2
|
+
function bindHold(target, options, signal) {
|
|
3
|
+
let holder;
|
|
4
|
+
const hold = {
|
|
5
|
+
hold(by) {
|
|
6
|
+
if (holder !== void 0 || !options.canHold()) return false;
|
|
7
|
+
holder = by;
|
|
8
|
+
options.onHold();
|
|
9
|
+
return true;
|
|
10
|
+
},
|
|
11
|
+
release() {
|
|
12
|
+
if (holder === void 0) return;
|
|
13
|
+
holder = void 0;
|
|
14
|
+
options.onRelease();
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
const releasePointer = (event) => {
|
|
18
|
+
if (event.pointerId === holder) hold.release();
|
|
19
|
+
};
|
|
20
|
+
target.addEventListener("lostpointercapture", releasePointer, { signal });
|
|
21
|
+
target.addEventListener("pointercancel", releasePointer, { signal });
|
|
22
|
+
target.addEventListener("pointerup", releasePointer, { signal });
|
|
23
|
+
target.addEventListener("keyup", (event) => {
|
|
24
|
+
if (event.key === "Meta" && typeof holder === "string" || event.key === holder) hold.release();
|
|
25
|
+
}, { signal });
|
|
26
|
+
target.addEventListener(options.leave, (event) => {
|
|
27
|
+
const next = event.relatedTarget;
|
|
28
|
+
const isInside = next instanceof Node && target.contains(next);
|
|
29
|
+
if (typeof holder === "string" && !isInside) hold.release();
|
|
30
|
+
}, { signal });
|
|
31
|
+
signal.addEventListener("abort", () => {
|
|
32
|
+
hold.release();
|
|
33
|
+
}, { once: true });
|
|
34
|
+
return hold;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
//#endregion
|
|
38
|
+
export { bindHold };
|
|
39
|
+
//# sourceMappingURL=hold.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hold.js","names":[],"sources":["../../../src/lib/hold.ts"],"sourcesContent":["export interface Hold {\n\thold(by: number | string): boolean;\n\trelease(): void;\n}\n\ninterface HoldOptions {\n\tcanHold: () => boolean;\n\tleave: 'blur' | 'focusout';\n\tonHold: () => void;\n\tonRelease: () => void;\n}\n\nexport function bindHold(target: HTMLElement, options: HoldOptions, signal: AbortSignal): Hold {\n\tlet holder: number | string | undefined;\n\tconst hold: Hold = {\n\t\thold(by) {\n\t\t\tif (holder !== undefined || !options.canHold()) return false;\n\n\t\t\tholder = by;\n\t\t\toptions.onHold();\n\n\t\t\treturn true;\n\t\t},\n\t\trelease() {\n\t\t\tif (holder === undefined) return;\n\n\t\t\tholder = undefined;\n\t\t\toptions.onRelease();\n\t\t},\n\t};\n\tconst releasePointer = (event: PointerEvent): void => {\n\t\tif (event.pointerId === holder) hold.release();\n\t};\n\n\ttarget.addEventListener('lostpointercapture', releasePointer, { signal });\n\ttarget.addEventListener('pointercancel', releasePointer, { signal });\n\ttarget.addEventListener('pointerup', releasePointer, { signal });\n\ttarget.addEventListener(\n\t\t'keyup',\n\t\t(event) => {\n\t\t\t// macOS sends no `keyup` for a key let go while Cmd is down\n\t\t\tconst isMetaLift = event.key === 'Meta' && typeof holder === 'string';\n\n\t\t\tif (isMetaLift || event.key === holder) hold.release();\n\t\t},\n\t\t{ signal },\n\t);\n\ttarget.addEventListener(\n\t\toptions.leave,\n\t\t(event) => {\n\t\t\tconst next = event.relatedTarget;\n\t\t\tconst isInside = next instanceof Node && target.contains(next);\n\n\t\t\tif (typeof holder === 'string' && !isInside) hold.release();\n\t\t},\n\t\t{ signal },\n\t);\n\tsignal.addEventListener(\n\t\t'abort',\n\t\t() => {\n\t\t\thold.release();\n\t\t},\n\t\t{ once: true },\n\t);\n\n\treturn hold;\n}\n"],"mappings":";AAYA,SAAgB,SAAS,QAAqB,SAAsB,QAA2B;CAC9F,IAAI;CACJ,MAAM,OAAa;EAClB,KAAK,IAAI;GACR,IAAI,WAAW,UAAa,CAAC,QAAQ,QAAQ,GAAG,OAAO;GAEvD,SAAS;GACT,QAAQ,OAAO;GAEf,OAAO;EACR;EACA,UAAU;GACT,IAAI,WAAW,QAAW;GAE1B,SAAS;GACT,QAAQ,UAAU;EACnB;CACD;CACA,MAAM,kBAAkB,UAA8B;EACrD,IAAI,MAAM,cAAc,QAAQ,KAAK,QAAQ;CAC9C;CAEA,OAAO,iBAAiB,sBAAsB,gBAAgB,EAAE,OAAO,CAAC;CACxE,OAAO,iBAAiB,iBAAiB,gBAAgB,EAAE,OAAO,CAAC;CACnE,OAAO,iBAAiB,aAAa,gBAAgB,EAAE,OAAO,CAAC;CAC/D,OAAO,iBACN,UACC,UAAU;EAIV,IAFmB,MAAM,QAAQ,UAAU,OAAO,WAAW,YAE3C,MAAM,QAAQ,QAAQ,KAAK,QAAQ;CACtD,GACA,EAAE,OAAO,CACV;CACA,OAAO,iBACN,QAAQ,QACP,UAAU;EACV,MAAM,OAAO,MAAM;EACnB,MAAM,WAAW,gBAAgB,QAAQ,OAAO,SAAS,IAAI;EAE7D,IAAI,OAAO,WAAW,YAAY,CAAC,UAAU,KAAK,QAAQ;CAC3D,GACA,EAAE,OAAO,CACV;CACA,OAAO,iBACN,eACM;EACL,KAAK,QAAQ;CACd,GACA,EAAE,MAAM,KAAK,CACd;CAEA,OAAO;AACR"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { readPxProperty } from "./read-px-property.js";
|
|
2
|
+
|
|
3
|
+
//#region src/lib/marker-band.ts
|
|
4
|
+
const kindPattern = /^[a-z][a-z0-9-]*$/;
|
|
5
|
+
const snapSlopPx = 4;
|
|
6
|
+
function renderMarker(marker, [from, to]) {
|
|
7
|
+
const part = document.createElement("div");
|
|
8
|
+
part.className = marker.end === void 0 ? "sonic-wavestrip-marker" : "sonic-wavestrip-region";
|
|
9
|
+
part.style.setProperty("--_sonic-marker-from", String(from));
|
|
10
|
+
part.style.setProperty("--_sonic-marker-to", String(to));
|
|
11
|
+
if (marker.kind !== void 0 && kindPattern.test(marker.kind)) part.style.setProperty("--_sonic-marker", `var(--sonic-marker-${marker.kind}, var(--_sonic-marker-default))`);
|
|
12
|
+
if (marker.dimmed === true) part.dataset.sonicDimmed = "";
|
|
13
|
+
return part;
|
|
14
|
+
}
|
|
15
|
+
function createMarkerBand(band) {
|
|
16
|
+
return {
|
|
17
|
+
reachPx: () => readPxProperty(getComputedStyle(band), "--_sonic-marker-size", 0) / 2 + snapSlopPx,
|
|
18
|
+
render: (markers, proportions) => {
|
|
19
|
+
band.replaceChildren(...markers.map((marker, index) => renderMarker(marker, proportions[index] ?? [0, 0])));
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
function nearestMarker(proportion, markerProportions, reach) {
|
|
24
|
+
let nearest;
|
|
25
|
+
let nearestDistance = reach;
|
|
26
|
+
for (const [index, markerProportion] of markerProportions.entries()) {
|
|
27
|
+
const distance = Math.abs(markerProportion - proportion);
|
|
28
|
+
if (distance > nearestDistance) continue;
|
|
29
|
+
nearest = index;
|
|
30
|
+
nearestDistance = distance;
|
|
31
|
+
}
|
|
32
|
+
return nearest;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
//#endregion
|
|
36
|
+
export { createMarkerBand, nearestMarker };
|
|
37
|
+
//# sourceMappingURL=marker-band.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"marker-band.js","names":[],"sources":["../../../src/lib/marker-band.ts"],"sourcesContent":["import { readPxProperty } from '#lib/read-px-property.ts';\n\ninterface MarkerLook {\n\tdimmed?: boolean;\n\tend?: number;\n\tkind?: string;\n}\n\ninterface MarkerBand {\n\treachPx: () => number;\n\trender: (\n\t\tmarkers: ReadonlyArray<MarkerLook>,\n\t\tproportions: ReadonlyArray<[number, number]>,\n\t) => void;\n}\n\n// Interpolated into a style, so anything else falls back to the default colour\nconst kindPattern = /^[a-z][a-z0-9-]*$/;\n\nconst snapSlopPx = 4;\n\nfunction renderMarker(marker: MarkerLook, [from, to]: [number, number]): HTMLElement {\n\tconst part = document.createElement('div');\n\n\tpart.className = marker.end === undefined ? 'sonic-wavestrip-marker' : 'sonic-wavestrip-region';\n\tpart.style.setProperty('--_sonic-marker-from', String(from));\n\tpart.style.setProperty('--_sonic-marker-to', String(to));\n\tif (marker.kind !== undefined && kindPattern.test(marker.kind)) {\n\t\tpart.style.setProperty(\n\t\t\t'--_sonic-marker',\n\t\t\t`var(--sonic-marker-${marker.kind}, var(--_sonic-marker-default))`,\n\t\t);\n\t}\n\tif (marker.dimmed === true) part.dataset.sonicDimmed = '';\n\n\treturn part;\n}\n\nexport function createMarkerBand(band: HTMLElement): MarkerBand {\n\treturn {\n\t\treachPx: () =>\n\t\t\treadPxProperty(getComputedStyle(band), '--_sonic-marker-size', 0) / 2 + snapSlopPx,\n\t\trender: (markers, proportions) => {\n\t\t\tband.replaceChildren(\n\t\t\t\t...markers.map((marker, index) => renderMarker(marker, proportions[index] ?? [0, 0])),\n\t\t\t);\n\t\t},\n\t};\n}\n\nexport function nearestMarker(\n\tproportion: number,\n\tmarkerProportions: ReadonlyArray<number>,\n\treach: number,\n): number | undefined {\n\tlet nearest: number | undefined;\n\tlet nearestDistance = reach;\n\n\tfor (const [index, markerProportion] of markerProportions.entries()) {\n\t\tconst distance = Math.abs(markerProportion - proportion);\n\t\tif (distance > nearestDistance) continue;\n\n\t\tnearest = index;\n\t\tnearestDistance = distance;\n\t}\n\n\treturn nearest;\n}\n"],"mappings":";;;AAiBA,MAAM,cAAc;AAEpB,MAAM,aAAa;AAEnB,SAAS,aAAa,QAAoB,CAAC,MAAM,KAAoC;CACpF,MAAM,OAAO,SAAS,cAAc,KAAK;CAEzC,KAAK,YAAY,OAAO,QAAQ,SAAY,2BAA2B;CACvE,KAAK,MAAM,YAAY,wBAAwB,OAAO,IAAI,CAAC;CAC3D,KAAK,MAAM,YAAY,sBAAsB,OAAO,EAAE,CAAC;CACvD,IAAI,OAAO,SAAS,UAAa,YAAY,KAAK,OAAO,IAAI,GAC5D,KAAK,MAAM,YACV,mBACA,sBAAsB,OAAO,KAAK,gCACnC;CAED,IAAI,OAAO,WAAW,MAAM,KAAK,QAAQ,cAAc;CAEvD,OAAO;AACR;AAEA,SAAgB,iBAAiB,MAA+B;CAC/D,OAAO;EACN,eACC,eAAe,iBAAiB,IAAI,GAAG,wBAAwB,CAAC,IAAI,IAAI;EACzE,SAAS,SAAS,gBAAgB;GACjC,KAAK,gBACJ,GAAG,QAAQ,KAAK,QAAQ,UAAU,aAAa,QAAQ,YAAY,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,CACrF;EACD;CACD;AACD;AAEA,SAAgB,cACf,YACA,mBACA,OACqB;CACrB,IAAI;CACJ,IAAI,kBAAkB;CAEtB,KAAK,MAAM,CAAC,OAAO,qBAAqB,kBAAkB,QAAQ,GAAG;EACpE,MAAM,WAAW,KAAK,IAAI,mBAAmB,UAAU;EACvD,IAAI,WAAW,iBAAiB;EAEhC,UAAU;EACV,kBAAkB;CACnB;CAEA,OAAO;AACR"}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { clamp } from "./math.js";
|
|
2
|
+
import { requireChild } from "./render.js";
|
|
3
|
+
import { readPxProperty } from "./read-px-property.js";
|
|
4
|
+
|
|
5
|
+
//#region src/lib/marker-rider.ts
|
|
6
|
+
const fadeShare = .25;
|
|
7
|
+
function lineX(seconds, view) {
|
|
8
|
+
return (seconds - view.startSeconds) / view.windowSeconds * view.widthPx;
|
|
9
|
+
}
|
|
10
|
+
function parkedPlacement(labels, index, [view, arrivingX]) {
|
|
11
|
+
const parked = labels[index];
|
|
12
|
+
if (!parked) return void 0;
|
|
13
|
+
const next = labels[index + 1];
|
|
14
|
+
const x = Math.max(view.insetPx, lineX(parked.start, view) + view.insetPx);
|
|
15
|
+
const approachPx = next ? lineX(next.start, view) - lineX(view.playheadSeconds, view) : Infinity;
|
|
16
|
+
return {
|
|
17
|
+
index,
|
|
18
|
+
opacity: clamp(approachPx / (view.widthPx * fadeShare), 0, 1),
|
|
19
|
+
shownPx: Math.min(parked.widthPx, arrivingX - x, view.widthPx - x),
|
|
20
|
+
x
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
function isDrawable(length) {
|
|
24
|
+
return Number.isFinite(length) && length > 0;
|
|
25
|
+
}
|
|
26
|
+
function layoutRider(labels, view) {
|
|
27
|
+
if (!isDrawable(view.windowSeconds) || !isDrawable(view.widthPx)) return {};
|
|
28
|
+
const parkedIndex = labels.findLastIndex((label) => label.start <= view.playheadSeconds);
|
|
29
|
+
const next = labels[parkedIndex + 1];
|
|
30
|
+
const arrivingX = next ? lineX(next.start, view) + view.insetPx : Infinity;
|
|
31
|
+
const parked = parkedPlacement(labels, parkedIndex, [view, arrivingX]);
|
|
32
|
+
const arriving = next && arrivingX < view.widthPx ? {
|
|
33
|
+
index: parkedIndex + 1,
|
|
34
|
+
opacity: 1,
|
|
35
|
+
shownPx: Math.min(next.widthPx, view.widthPx - arrivingX),
|
|
36
|
+
x: arrivingX
|
|
37
|
+
} : void 0;
|
|
38
|
+
return {
|
|
39
|
+
...arriving ? { arriving } : {},
|
|
40
|
+
...parked ? { parked } : {}
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
function labelSlot(element) {
|
|
44
|
+
return {
|
|
45
|
+
element,
|
|
46
|
+
opacity: NaN,
|
|
47
|
+
shownPx: NaN,
|
|
48
|
+
x: NaN
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
function writePlacement(slot, { opacity, shownPx, x }, widthPx) {
|
|
52
|
+
const { style } = slot.element;
|
|
53
|
+
if (slot.x !== x) {
|
|
54
|
+
slot.x = x;
|
|
55
|
+
style.setProperty("translate", `${x.toFixed(2)}px`);
|
|
56
|
+
}
|
|
57
|
+
if (slot.shownPx !== shownPx) {
|
|
58
|
+
slot.shownPx = shownPx;
|
|
59
|
+
style.setProperty("clip-path", shownPx < widthPx ? `inset(0 calc(100% - ${shownPx.toFixed(2)}px) 0 0)` : "");
|
|
60
|
+
}
|
|
61
|
+
if (slot.opacity !== opacity) {
|
|
62
|
+
slot.opacity = opacity;
|
|
63
|
+
style.setProperty("opacity", opacity < 1 ? opacity.toFixed(3) : "");
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
function writeLabel(slot, labels, placement) {
|
|
67
|
+
const { element } = slot;
|
|
68
|
+
const label = placement && placement.shownPx > 0 ? labels[placement.index] : void 0;
|
|
69
|
+
if (element.hidden !== !label) element.hidden = !label;
|
|
70
|
+
if (!label || !placement) return;
|
|
71
|
+
if (element.textContent !== label.text) element.textContent = label.text;
|
|
72
|
+
element.toggleAttribute("data-sonic-dimmed", label.isDimmed);
|
|
73
|
+
writePlacement(slot, placement, label.widthPx);
|
|
74
|
+
}
|
|
75
|
+
function measureWidths(control, className, texts) {
|
|
76
|
+
const probes = texts.map((text) => {
|
|
77
|
+
const probe = document.createElement("div");
|
|
78
|
+
probe.className = className;
|
|
79
|
+
probe.textContent = text;
|
|
80
|
+
return probe;
|
|
81
|
+
});
|
|
82
|
+
control.append(...probes);
|
|
83
|
+
const widths = probes.map((probe) => probe.getBoundingClientRect().width);
|
|
84
|
+
for (const probe of probes) probe.remove();
|
|
85
|
+
return widths;
|
|
86
|
+
}
|
|
87
|
+
function createLabelRider(control, options) {
|
|
88
|
+
const { className, insetProperty } = options;
|
|
89
|
+
const parked = labelSlot(requireChild(control, `.${className}`, HTMLElement));
|
|
90
|
+
const arriving = labelSlot(requireChild(control, `.${className} + .${className}`, HTMLElement));
|
|
91
|
+
let insetPx = 0;
|
|
92
|
+
let measured = [];
|
|
93
|
+
return {
|
|
94
|
+
measure: (labels) => {
|
|
95
|
+
const widths = measureWidths(control, className, labels.map(({ text }) => text));
|
|
96
|
+
insetPx = readPxProperty(getComputedStyle(control), insetProperty, 0);
|
|
97
|
+
measured = labels.map((label, index) => ({
|
|
98
|
+
...label,
|
|
99
|
+
widthPx: widths[index] ?? 0
|
|
100
|
+
})).toSorted((first, second) => first.start - second.start);
|
|
101
|
+
},
|
|
102
|
+
place: (window) => {
|
|
103
|
+
const layout = layoutRider(measured, {
|
|
104
|
+
...window,
|
|
105
|
+
insetPx
|
|
106
|
+
});
|
|
107
|
+
writeLabel(parked, measured, layout.parked);
|
|
108
|
+
writeLabel(arriving, measured, layout.arriving);
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
//#endregion
|
|
114
|
+
export { createLabelRider, layoutRider };
|
|
115
|
+
//# sourceMappingURL=marker-rider.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"marker-rider.js","names":[],"sources":["../../../src/lib/marker-rider.ts"],"sourcesContent":["import { clamp } from '#lib/math.ts';\nimport { readPxProperty } from '#lib/read-px-property.ts';\nimport { requireChild } from '#lib/render.ts';\n\ninterface RiderWindow {\n\tplayheadSeconds: number;\n\tstartSeconds: number;\n\twidthPx: number;\n\twindowSeconds: number;\n}\n\nexport interface RiderView extends RiderWindow {\n\tinsetPx: number;\n}\n\nexport interface RiderLabel {\n\tisDimmed: boolean;\n\tstart: number;\n\ttext: string;\n\twidthPx: number;\n}\n\ninterface LabelPlacement {\n\tindex: number;\n\topacity: number;\n\tshownPx: number;\n\tx: number;\n}\n\ninterface RiderLayout {\n\tarriving?: LabelPlacement;\n\tparked?: LabelPlacement;\n}\n\ninterface LabelSlot {\n\telement: HTMLElement;\n\topacity: number;\n\tshownPx: number;\n\tx: number;\n}\n\nconst fadeShare = 0.25;\n\nfunction lineX(seconds: number, view: RiderView): number {\n\treturn ((seconds - view.startSeconds) / view.windowSeconds) * view.widthPx;\n}\n\nfunction parkedPlacement(\n\tlabels: ReadonlyArray<RiderLabel>,\n\tindex: number,\n\t[view, arrivingX]: [RiderView, number],\n): LabelPlacement | undefined {\n\tconst parked = labels[index];\n\tif (!parked) return undefined;\n\n\tconst next = labels[index + 1];\n\tconst x = Math.max(view.insetPx, lineX(parked.start, view) + view.insetPx);\n\tconst approachPx = next ? lineX(next.start, view) - lineX(view.playheadSeconds, view) : Infinity;\n\n\treturn {\n\t\tindex,\n\t\topacity: clamp(approachPx / (view.widthPx * fadeShare), 0, 1),\n\t\tshownPx: Math.min(parked.widthPx, arrivingX - x, view.widthPx - x),\n\t\tx,\n\t};\n}\n\nfunction isDrawable(length: number): boolean {\n\treturn Number.isFinite(length) && length > 0;\n}\n\nexport function layoutRider(labels: ReadonlyArray<RiderLabel>, view: RiderView): RiderLayout {\n\tif (!isDrawable(view.windowSeconds) || !isDrawable(view.widthPx)) return {};\n\n\tconst parkedIndex = labels.findLastIndex((label) => label.start <= view.playheadSeconds);\n\tconst next = labels[parkedIndex + 1];\n\tconst arrivingX = next ? lineX(next.start, view) + view.insetPx : Infinity;\n\tconst parked = parkedPlacement(labels, parkedIndex, [view, arrivingX]);\n\tconst arriving: LabelPlacement | undefined =\n\t\tnext && arrivingX < view.widthPx\n\t\t\t? {\n\t\t\t\t\tindex: parkedIndex + 1,\n\t\t\t\t\topacity: 1,\n\t\t\t\t\tshownPx: Math.min(next.widthPx, view.widthPx - arrivingX),\n\t\t\t\t\tx: arrivingX,\n\t\t\t\t}\n\t\t\t: undefined;\n\n\treturn { ...(arriving ? { arriving } : {}), ...(parked ? { parked } : {}) };\n}\n\nfunction labelSlot(element: HTMLElement): LabelSlot {\n\treturn { element, opacity: NaN, shownPx: NaN, x: NaN };\n}\n\nfunction writePlacement(\n\tslot: LabelSlot,\n\t{ opacity, shownPx, x }: LabelPlacement,\n\twidthPx: number,\n): void {\n\tconst { style } = slot.element;\n\n\tif (slot.x !== x) {\n\t\tslot.x = x;\n\t\tstyle.setProperty('translate', `${x.toFixed(2)}px`);\n\t}\n\tif (slot.shownPx !== shownPx) {\n\t\tslot.shownPx = shownPx;\n\t\tstyle.setProperty(\n\t\t\t'clip-path',\n\t\t\tshownPx < widthPx ? `inset(0 calc(100% - ${shownPx.toFixed(2)}px) 0 0)` : '',\n\t\t);\n\t}\n\tif (slot.opacity !== opacity) {\n\t\tslot.opacity = opacity;\n\t\tstyle.setProperty('opacity', opacity < 1 ? opacity.toFixed(3) : '');\n\t}\n}\n\nfunction writeLabel(\n\tslot: LabelSlot,\n\tlabels: ReadonlyArray<RiderLabel>,\n\tplacement: LabelPlacement | undefined,\n): void {\n\tconst { element } = slot;\n\tconst label = placement && placement.shownPx > 0 ? labels[placement.index] : undefined;\n\n\tif (element.hidden !== !label) element.hidden = !label;\n\tif (!label || !placement) return;\n\n\tif (element.textContent !== label.text) element.textContent = label.text;\n\telement.toggleAttribute('data-sonic-dimmed', label.isDimmed);\n\twritePlacement(slot, placement, label.widthPx);\n}\n\nfunction measureWidths(\n\tcontrol: HTMLElement,\n\tclassName: string,\n\ttexts: Array<string>,\n): Array<number> {\n\tconst probes = texts.map((text) => {\n\t\tconst probe = document.createElement('div');\n\n\t\tprobe.className = className;\n\t\tprobe.textContent = text;\n\n\t\treturn probe;\n\t});\n\n\tcontrol.append(...probes);\n\n\tconst widths = probes.map((probe) => probe.getBoundingClientRect().width);\n\n\tfor (const probe of probes) probe.remove();\n\n\treturn widths;\n}\n\nexport function createLabelRider(\n\tcontrol: HTMLElement,\n\toptions: { className: string; insetProperty: `--_sonic-${string}` },\n): {\n\tmeasure: (labels: ReadonlyArray<Omit<RiderLabel, 'widthPx'>>) => void;\n\tplace: (window: RiderWindow) => void;\n} {\n\tconst { className, insetProperty } = options;\n\tconst parked = labelSlot(requireChild(control, `.${className}`, HTMLElement));\n\tconst arriving = labelSlot(requireChild(control, `.${className} + .${className}`, HTMLElement));\n\tlet insetPx = 0;\n\tlet measured: Array<RiderLabel> = [];\n\n\treturn {\n\t\tmeasure: (labels) => {\n\t\t\tconst widths = measureWidths(\n\t\t\t\tcontrol,\n\t\t\t\tclassName,\n\t\t\t\tlabels.map(({ text }) => text),\n\t\t\t);\n\n\t\t\tinsetPx = readPxProperty(getComputedStyle(control), insetProperty, 0);\n\t\t\tmeasured = labels\n\t\t\t\t.map((label, index) => ({ ...label, widthPx: widths[index] ?? 0 }))\n\t\t\t\t.toSorted((first, second) => first.start - second.start);\n\t\t},\n\t\tplace: (window) => {\n\t\t\tconst layout = layoutRider(measured, { ...window, insetPx });\n\n\t\t\twriteLabel(parked, measured, layout.parked);\n\t\t\twriteLabel(arriving, measured, layout.arriving);\n\t\t},\n\t};\n}\n"],"mappings":"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"}
|