@neo-ui-kit/react 1.3.13 → 1.3.15

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 CHANGED
@@ -4,6 +4,20 @@ Migration: replace `@ad-voice/ui` with `@neo-ui-kit/react` in dependencies and i
4
4
 
5
5
  NEO UI — React + TypeScript UI kit: glass/ruby surfaces, animated neon borders, fields, tabs, dialogs, audio controls and a melody editor.
6
6
 
7
+ ## 1.3.15
8
+
9
+ Animated borders use a shared WebGPU pipeline when available, combining the existing masks and glow textures in one pass. Rendering is limited to the moving lights. Canvas 2D and SVG remain fallback paths; speeds, colors and effects are retained. See the performance report for laptop measurements and pixel-comparison limits.
10
+
11
+ ## 1.3.14
12
+
13
+ Animated borders composite their moving lights in bounded buffers instead of repainting full-size intermediate surfaces. The same masks, glow textures, speeds and overlapping-light composition are preserved.
14
+
15
+ LevelMeter computes each sample's SVG coordinates once, writes only the final contour after a delayed frame, and skips identical contours. Sampling, attack/release, SVG geometry, gradients and glow remain unchanged. CPU ×8/×16 measurements and limitations are recorded in the performance report; this does not establish a universal 60 FPS guarantee.
16
+
17
+ The unchanged SVG subtree is reused across signal updates. A committed value reaches the renderer before the next paint, including when waking from silence; accessible values still update with the signal.
18
+
19
+ LevelMeter renders its waveform in a shared worker when supported, retaining the SVG fallback and existing glow. Its accessible values remain on the main thread.
20
+
7
21
  ## 1.3.13
8
22
 
9
23
  MelodyRoll skips repainting empty glow/voice/trail layers and a settled keyboard. A disappearing voice still clears its last frame, and keyboard hover, press and hit transitions retain their final frame. See the [performance report](https://github.com/Pro100Dimka/adui/blob/main/evidence/performance-report.md) for karaoke measurements and remaining limits under CPU ×16.
@@ -51,7 +65,7 @@ import "@neo-ui-kit/react/styles.css";
51
65
  To test repository changes before publishing, run `npm run package` from the repository root and install the resulting archive in a separate application:
52
66
 
53
67
  ```bash
54
- npm install /path/to/adui/release/neo-ui-kit-react-1.3.13.tgz
68
+ npm install /path/to/adui/release/neo-ui-kit-react-1.3.15.tgz
55
69
  ```
56
70
 
57
71
  Maintainers publish new versions from `Pro100Dimka/adui` using the `release.yml` GitHub Actions workflow, a matching `v<version>` tag, and the `npm-publication` environment. npm trusts its OIDC identity; no `NPM_TOKEN` is required. The workflow checks the project and packed consumer before publishing the tested archive. Published `1.0.0` cannot be overwritten: code and README changes in npm require a new package version. See the [repository release instructions](https://github.com/Pro100Dimka/adui#npm-пакет).
@@ -11,32 +11,207 @@ import {
11
11
 
12
12
  // src/core/border-worker.js
13
13
  function renderBorders() {
14
+ async function createGpuPainter() {
15
+ if (!navigator.gpu) return null;
16
+ const adapter = await navigator.gpu.requestAdapter();
17
+ if (!adapter) return null;
18
+ const device = await adapter.requestDevice();
19
+ const format = navigator.gpu.getPreferredCanvasFormat();
20
+ const shader = device.createShaderModule({ code: `
21
+ struct Uniforms { size: vec4f, canvas: vec4f, lights: vec4f, padding: vec4f };
22
+ @group(0) @binding(0) var<uniform> u: Uniforms;
23
+ @group(0) @binding(1) var linearSampler: sampler;
24
+ @group(0) @binding(2) var mask0: texture_2d<f32>;
25
+ @group(0) @binding(3) var glow0: texture_2d<f32>;
26
+ @group(0) @binding(4) var mask1: texture_2d<f32>;
27
+ @group(0) @binding(5) var glow1: texture_2d<f32>;
28
+ @vertex fn vertex(@builtin(vertex_index) i: u32) -> @builtin(position) vec4f {
29
+ let positions = array<vec2f, 3>(vec2f(-1,-1), vec2f(3,-1), vec2f(-1,3));
30
+ return vec4f(positions[i], 0, 1);
31
+ }
32
+ fn spot(p: vec2f, origin: vec2f, texture: texture_2d<f32>) -> vec4f {
33
+ let uv = (p - origin) / (u.size.w * 2);
34
+ if (any(uv < vec2f(0)) || any(uv > vec2f(1))) { return vec4f(0); }
35
+ return textureSampleLevel(texture, linearSampler, uv, 0);
36
+ }
37
+ fn layer(p: vec2f, mask: texture_2d<f32>, glow: texture_2d<f32>) -> vec4f {
38
+ let alpha = textureSampleLevel(mask, linearSampler, p / u.size.xy, 0).a;
39
+ if (alpha == 0) { return vec4f(0); }
40
+ let first = spot(p, u.lights.xy, glow);
41
+ let second = spot(p, u.lights.zw, glow);
42
+ return (second + first * (1 - second.a)) * alpha;
43
+ }
44
+ fn filtered(q: vec2f, mask: texture_2d<f32>, glow: texture_2d<f32>) -> vec4f {
45
+ let base = floor(q - vec2f(.5)) + vec2f(.5);
46
+ let f = fract(q - vec2f(.5));
47
+ let a = layer(base / u.size.z, mask, glow);
48
+ let b = layer((base + vec2f(1,0)) / u.size.z, mask, glow);
49
+ let c = layer((base + vec2f(0,1)) / u.size.z, mask, glow);
50
+ let d = layer((base + vec2f(1,1)) / u.size.z, mask, glow);
51
+ return mix(mix(a,b,f.x),mix(c,d,f.x),f.y);
52
+ }
53
+ @fragment fn fragment(@builtin(position) p: vec4f) -> @location(0) vec4f {
54
+ let q = p.xy * u.canvas.xy / (u.size.xy * u.size.z);
55
+ let aura = filtered(q, mask0, glow0) * u.canvas.z;
56
+ let core = filtered(q, mask1, glow1) * u.canvas.w;
57
+ return core + aura * (1-core.a);
58
+ }
59
+ ` });
60
+ let pipeline;
61
+ try {
62
+ pipeline = await device.createRenderPipelineAsync({
63
+ layout: "auto",
64
+ vertex: { module: shader, entryPoint: "vertex" },
65
+ fragment: { module: shader, entryPoint: "fragment", targets: [{ format }] },
66
+ primitive: { topology: "triangle-list" }
67
+ });
68
+ } catch (error) {
69
+ device.destroy();
70
+ throw error;
71
+ }
72
+ const sampler = device.createSampler({ magFilter: "linear", minFilter: "linear" });
73
+ device.addEventListener("uncapturederror", (event) => {
74
+ throw event.error;
75
+ });
76
+ device.lost.then((info) => {
77
+ setTimeout(() => {
78
+ throw new Error(info.message || "Border GPU device lost");
79
+ });
80
+ });
81
+ return (scene) => {
82
+ const context = scene.canvas.getContext("webgpu");
83
+ context.configure({ device, format, alphaMode: "premultiplied" });
84
+ const textures = scene.layers.flatMap((layer) => [layer.mask, layer.texture]).map((image) => {
85
+ const texture = device.createTexture({
86
+ size: [image.width, image.height],
87
+ format: "rgba8unorm",
88
+ usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST | GPUTextureUsage.RENDER_ATTACHMENT
89
+ });
90
+ device.queue.copyExternalImageToTexture({ source: image }, { texture, premultipliedAlpha: true }, [image.width, image.height]);
91
+ return texture;
92
+ });
93
+ const buffer = device.createBuffer({ size: 64, usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST });
94
+ const values = new Float32Array(16);
95
+ values.set([
96
+ scene.width,
97
+ scene.height,
98
+ scene.dpr,
99
+ scene.radius,
100
+ scene.canvas.width,
101
+ scene.canvas.height,
102
+ scene.layers[0].opacity,
103
+ scene.layers[1].opacity
104
+ ]);
105
+ const bindGroup = device.createBindGroup({ layout: pipeline.getBindGroupLayout(0), entries: [
106
+ { binding: 0, resource: { buffer } },
107
+ { binding: 1, resource: sampler },
108
+ ...textures.map((texture, index) => ({ binding: index + 2, resource: texture.createView() }))
109
+ ] });
110
+ return {
111
+ draw(current) {
112
+ current.lights.forEach((light, index) => {
113
+ const at = (light.phase + current.elapsed * light.speed / current.length) % 1 * 1024;
114
+ const lo = Math.floor(at), hi = (lo + 1) % 1024, weight = at - lo;
115
+ for (let axis = 0; axis < 2; axis++) values[8 + index * 2 + axis] = current.points[lo * 2 + axis] * (1 - weight) + current.points[hi * 2 + axis] * weight - current.radius + 16;
116
+ });
117
+ device.queue.writeBuffer(buffer, 0, values);
118
+ const encoder = device.createCommandEncoder();
119
+ const pass = encoder.beginRenderPass({ colorAttachments: [{
120
+ view: context.getCurrentTexture().createView(),
121
+ loadOp: "clear",
122
+ storeOp: "store",
123
+ clearValue: [0, 0, 0, 0]
124
+ }] });
125
+ pass.setPipeline(pipeline);
126
+ pass.setBindGroup(0, bindGroup);
127
+ for (let i = 0; i < 2; i++) {
128
+ const sx = current.width * current.dpr / current.canvas.width, sy = current.height * current.dpr / current.canvas.height;
129
+ const x = Math.max(0, Math.floor((values[8 + i * 2] * current.dpr - 2) * sx));
130
+ const y = Math.max(0, Math.floor((values[9 + i * 2] * current.dpr - 2) * sy));
131
+ const r = Math.min(current.canvas.width, Math.ceil(((values[8 + i * 2] + current.radius * 2) * current.dpr + 2) * sx));
132
+ const b = Math.min(current.canvas.height, Math.ceil(((values[9 + i * 2] + current.radius * 2) * current.dpr + 2) * sy));
133
+ if (r > x && b > y) {
134
+ pass.setScissorRect(x, y, r - x, b - y);
135
+ pass.draw(3);
136
+ }
137
+ }
138
+ pass.end();
139
+ device.queue.submit([encoder.finish()]);
140
+ },
141
+ dispose() {
142
+ context.unconfigure();
143
+ buffer.destroy();
144
+ textures.forEach((texture) => texture.destroy());
145
+ }
146
+ };
147
+ };
148
+ }
149
+ let gpu;
150
+ const pending = [];
151
+ onmessage = (event) => pending.push(event);
14
152
  const scenes = /* @__PURE__ */ new Map();
15
153
  let frame = null, fps = 60, last2 = -Infinity;
16
154
  const release = (scene) => {
155
+ scene.gpu?.dispose();
17
156
  for (const layer of scene.layers) {
18
157
  layer.mask.close();
19
158
  layer.texture.close();
20
159
  }
21
160
  };
22
161
  const draw = (scene) => {
23
- const { ctx, width, height, points, length, radius } = scene;
162
+ if (scene.gpu) {
163
+ scene.gpu.draw(scene);
164
+ return;
165
+ }
166
+ const { ctx, width, height, points, length, radius, dpr } = scene;
167
+ const lights = scene.lights.map((light) => {
168
+ const index = (light.phase + scene.elapsed * light.speed / length) % 1 * 1024;
169
+ const lo = Math.floor(index), hi = (lo + 1) % 1024, weight = index - lo;
170
+ return {
171
+ x: points[lo * 2] * (1 - weight) + points[hi * 2] * weight - radius + 16,
172
+ y: points[lo * 2 + 1] * (1 - weight) + points[hi * 2 + 1] * weight - radius + 16
173
+ };
174
+ });
175
+ const regions = lights.map(({ x, y }) => ({
176
+ x: Math.max(0, Math.floor(x * dpr - 1) / dpr),
177
+ y: Math.max(0, Math.floor(y * dpr - 1) / dpr),
178
+ right: Math.min(scene.canvas.width, Math.ceil((x + radius * 2) * dpr + 1)) / dpr,
179
+ bottom: Math.min(scene.canvas.height, Math.ceil((y + radius * 2) * dpr + 1)) / dpr
180
+ }));
181
+ const [first, second] = regions;
182
+ if (second && first.x < second.right && second.x < first.right && first.y < second.bottom && second.y < first.bottom) {
183
+ regions.splice(0, 2, {
184
+ x: Math.min(first.x, second.x),
185
+ y: Math.min(first.y, second.y),
186
+ right: Math.max(first.right, second.right),
187
+ bottom: Math.max(first.bottom, second.bottom)
188
+ });
189
+ }
24
190
  ctx.clearRect(0, 0, width, height);
25
191
  for (const layer of scene.layers) {
26
192
  const paint = layer.ctx;
27
- paint.globalCompositeOperation = "source-over";
28
- paint.clearRect(0, 0, width, height);
29
- for (const light of scene.lights) {
30
- const index = (light.phase + scene.elapsed * light.speed / length) % 1 * 1024;
31
- const lo = Math.floor(index), hi = (lo + 1) % 1024, weight = index - lo;
32
- const x = points[lo * 2] * (1 - weight) + points[hi * 2] * weight;
33
- const y = points[lo * 2 + 1] * (1 - weight) + points[hi * 2 + 1] * weight;
34
- paint.drawImage(layer.texture, x - radius + 16, y - radius + 16, radius * 2, radius * 2);
35
- }
36
- paint.globalCompositeOperation = "destination-in";
37
- paint.drawImage(layer.mask, 0, 0, width, height);
38
193
  ctx.globalAlpha = layer.opacity;
39
- ctx.drawImage(layer.buffer, 0, 0, width, height);
194
+ for (const { x, y, right, bottom } of regions) {
195
+ const w = right - x, h = bottom - y;
196
+ paint.globalCompositeOperation = "source-over";
197
+ paint.clearRect(0, 0, layer.buffer.width / dpr, layer.buffer.height / dpr);
198
+ for (const light of lights)
199
+ paint.drawImage(layer.texture, light.x - x, light.y - y, radius * 2, radius * 2);
200
+ paint.globalCompositeOperation = "destination-in";
201
+ paint.drawImage(
202
+ layer.mask,
203
+ x * layer.mask.width / width,
204
+ y * layer.mask.height / height,
205
+ w * layer.mask.width / width,
206
+ h * layer.mask.height / height,
207
+ 0,
208
+ 0,
209
+ w,
210
+ h
211
+ );
212
+ const scaleX = width * dpr / scene.canvas.width, scaleY = height * dpr / scene.canvas.height;
213
+ ctx.drawImage(layer.buffer, 0, 0, w * dpr, h * dpr, x * scaleX, y * scaleY, w * scaleX, h * scaleY);
214
+ }
40
215
  }
41
216
  ctx.globalAlpha = 1;
42
217
  };
@@ -57,7 +232,7 @@ function renderBorders() {
57
232
  }
58
233
  schedule2();
59
234
  }
60
- onmessage = ({ data }) => {
235
+ const receive = ({ data }) => {
61
236
  const scene = scenes.get(data.id);
62
237
  if (data.type === "rate") fps = data.fps;
63
238
  else if (data.type === "remove") {
@@ -73,12 +248,18 @@ function renderBorders() {
73
248
  const next = { ...data, canvas: scene?.canvas ?? data.canvas, elapsed: scene?.elapsed ?? data.elapsed, previous: null };
74
249
  next.canvas.width = Math.ceil(data.width * data.dpr);
75
250
  next.canvas.height = Math.ceil(data.height * data.dpr);
76
- next.ctx = next.canvas.getContext("2d");
77
- next.ctx.setTransform(data.dpr, 0, 0, data.dpr, 0, 0);
78
- for (const layer of next.layers) {
79
- layer.buffer = new OffscreenCanvas(next.canvas.width, next.canvas.height);
80
- layer.ctx = layer.buffer.getContext("2d");
81
- layer.ctx.setTransform(data.dpr, 0, 0, data.dpr, 0, 0);
251
+ if (gpu) next.gpu = gpu(next);
252
+ else {
253
+ next.ctx = next.canvas.getContext("2d");
254
+ next.ctx.setTransform(data.dpr, 0, 0, data.dpr, 0, 0);
255
+ for (const layer of next.layers) {
256
+ layer.buffer = new OffscreenCanvas(
257
+ Math.min(next.canvas.width, Math.ceil(next.radius * 4 * data.dpr) + 4),
258
+ Math.min(next.canvas.height, Math.ceil(next.radius * 4 * data.dpr) + 4)
259
+ );
260
+ layer.ctx = layer.buffer.getContext("2d");
261
+ layer.ctx.setTransform(data.dpr, 0, 0, data.dpr, 0, 0);
262
+ }
82
263
  }
83
264
  scenes.set(data.id, next);
84
265
  draw(next);
@@ -86,6 +267,18 @@ function renderBorders() {
86
267
  }
87
268
  schedule2();
88
269
  };
270
+ createGpuPainter().catch((error) => {
271
+ console.warn("Border GPU initialization failed; using Canvas 2D", error);
272
+ return null;
273
+ }).then((value) => {
274
+ gpu = value;
275
+ onmessage = receive;
276
+ pending.splice(0).forEach(receive);
277
+ }).catch((error) => {
278
+ setTimeout(() => {
279
+ throw error;
280
+ });
281
+ });
89
282
  }
90
283
  var worker = null;
91
284
  var workerUrl = null;
@@ -2,7 +2,7 @@ import {
2
2
  themes,
3
3
  useBorder,
4
4
  usePauseOffscreen
5
- } from "./chunk-OCH27SNY.js";
5
+ } from "./chunk-6VPAU3TV.js";
6
6
  import {
7
7
  assignRef,
8
8
  clamp,
@@ -13,6 +13,16 @@
13
13
  0 0 0.22rem color-mix(in srgb, var(--ad-red) 78%, transparent)
14
14
  );
15
15
  }
16
+ .ad-level-meter-canvas {
17
+ position: absolute;
18
+ inset: 0;
19
+ width: 100%;
20
+ height: 100%;
21
+ pointer-events: none;
22
+ }
23
+ .ad-level-meter[data-ad-canvas] > svg {
24
+ visibility: hidden;
25
+ }
16
26
  .ad-level-meter-axis {
17
27
  stroke: color-mix(in srgb, var(--ad-red) 24%, transparent);
18
28
  stroke-width: 0.7;
package/dist/core.js CHANGED
@@ -11,7 +11,7 @@ import {
11
11
  useSmoothWheel,
12
12
  useTabShape,
13
13
  useTick
14
- } from "./chunks/chunk-OCH27SNY.js";
14
+ } from "./chunks/chunk-6VPAU3TV.js";
15
15
  import {
16
16
  LocaleProvider,
17
17
  addMessages,
package/dist/forms.js CHANGED
@@ -4,8 +4,8 @@ import {
4
4
  defaultFieldRegistry,
5
5
  useForm,
6
6
  useFormContext
7
- } from "./chunks/chunk-L6VCC7AS.js";
8
- import "./chunks/chunk-OCH27SNY.js";
7
+ } from "./chunks/chunk-TMCWA446.js";
8
+ import "./chunks/chunk-6VPAU3TV.js";
9
9
  import "./chunks/chunk-BJSFN52W.js";
10
10
  import "./chunks/chunk-FCOU3ODR.js";
11
11
  export {
package/dist/index.js CHANGED
@@ -62,7 +62,7 @@ import {
62
62
  useSvgId,
63
63
  variantMaterial,
64
64
  vectorElement
65
- } from "./chunks/chunk-L6VCC7AS.js";
65
+ } from "./chunks/chunk-TMCWA446.js";
66
66
  import {
67
67
  ThemeProvider,
68
68
  createNeonRenderer,
@@ -79,7 +79,7 @@ import {
79
79
  useTabShape,
80
80
  useThemePalette,
81
81
  useTick
82
- } from "./chunks/chunk-OCH27SNY.js";
82
+ } from "./chunks/chunk-6VPAU3TV.js";
83
83
  import {
84
84
  LocaleProvider,
85
85
  addMessages,
@@ -5968,20 +5968,262 @@ function PlaybackTrack({ audio, playing, position: controlledPosition, disabled,
5968
5968
  }
5969
5969
 
5970
5970
  // src/components/media/LevelMeter/LevelMeter.tsx
5971
- import { useEffect as useEffect13, useRef as useRef27 } from "react";
5971
+ import { useEffect as useEffect13, useLayoutEffect as useLayoutEffect5, useMemo as useMemo5, useRef as useRef27 } from "react";
5972
5972
  import { jsx as jsx48, jsxs as jsxs39 } from "react/jsx-runtime";
5973
5973
  var SAMPLES = 64;
5974
5974
  var WIDTH = 256;
5975
5975
  var HEIGHT = 24;
5976
5976
  var MID2 = HEIGHT / 2;
5977
5977
  var STEP = WIDTH / (SAMPLES - 1);
5978
+ var X = Array.from({ length: SAMPLES + 1 }, (_, i) => (i * STEP).toFixed(2));
5978
5979
  var SAMPLE_MS = 28;
5980
+ var waveSample = (level) => {
5981
+ const height = 1 + level ** 0.68 * (MID2 - 3);
5982
+ return { level, upper: (MID2 - height).toFixed(2), lower: (MID2 + height).toFixed(2) };
5983
+ };
5979
5984
  var wavePath2 = (samples) => {
5980
- const point = (i, level, side) => `${(i * STEP).toFixed(2)} ${(MID2 + side * (1 + level ** 0.68 * (MID2 - 3))).toFixed(2)}`;
5981
- const upper = samples.map((level, i) => point(i, level, -1));
5982
- const lower = samples.map((level, i) => point(i, level, 1)).reverse();
5985
+ const upper = samples.map((sample, i) => `${X[i]} ${sample.upper}`);
5986
+ const lower = samples.map((sample, i) => `${X[i]} ${sample.lower}`).reverse();
5983
5987
  return `M${upper.join("L")}L${lower.join("L")}Z`;
5984
5988
  };
5989
+ function renderMeters() {
5990
+ const scenes = /* @__PURE__ */ new Map();
5991
+ onmessage = ({ data }) => {
5992
+ if (data.remove !== void 0) {
5993
+ scenes.delete(data.remove);
5994
+ return;
5995
+ }
5996
+ const completed = [], failed = [];
5997
+ for (const frame of data.frames ?? []) {
5998
+ try {
5999
+ let scene = scenes.get(frame.id);
6000
+ if (frame.canvas) {
6001
+ const ctx2 = frame.canvas.getContext("2d");
6002
+ if (!ctx2 || !frame.style) throw new Error("Canvas unavailable");
6003
+ scene = { canvas: frame.canvas, ctx: ctx2, style: frame.style, fill: "", path: new Path2D(), revision: frame.revision };
6004
+ scenes.set(frame.id, scene);
6005
+ frame.canvas.addEventListener("contextlost", () => {
6006
+ scenes.delete(frame.id);
6007
+ postMessage({ failed: [frame.id] });
6008
+ });
6009
+ }
6010
+ if (!scene) continue;
6011
+ const { canvas, ctx } = scene;
6012
+ if (frame.style) {
6013
+ scene.style = frame.style;
6014
+ scene.revision = frame.revision;
6015
+ canvas.width = frame.style.width;
6016
+ canvas.height = frame.style.height;
6017
+ scene.fill = frame.style.fill;
6018
+ if (frame.style.stops.length) {
6019
+ const gradient = ctx.createLinearGradient(0, 0, 260.06, 0);
6020
+ for (const stop of frame.style.stops) gradient.addColorStop(stop.at, stop.color);
6021
+ scene.fill = gradient;
6022
+ }
6023
+ }
6024
+ if (frame.d !== void 0) scene.path = new Path2D(frame.d);
6025
+ ctx.setTransform(1, 0, 0, 1, 0, 0);
6026
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
6027
+ ctx.scale(canvas.width / 256, canvas.height / 24);
6028
+ ctx.globalAlpha = scene.style.lineAlpha;
6029
+ ctx.strokeStyle = scene.style.stroke;
6030
+ ctx.lineWidth = scene.style.lineWidth;
6031
+ ctx.beginPath();
6032
+ ctx.moveTo(0, 12);
6033
+ ctx.lineTo(256, 12);
6034
+ ctx.stroke();
6035
+ ctx.translate(frame.offset, 0);
6036
+ ctx.globalAlpha = scene.style.fillAlpha;
6037
+ ctx.fillStyle = scene.fill;
6038
+ ctx.fill(scene.path);
6039
+ completed.push({ id: frame.id, revision: scene.revision });
6040
+ } catch {
6041
+ scenes.delete(frame.id);
6042
+ failed.push(frame.id);
6043
+ }
6044
+ }
6045
+ postMessage({ ack: true, completed, failed });
6046
+ };
6047
+ }
6048
+ var meterWorker;
6049
+ var meterUrl;
6050
+ var meterSerial = 0;
6051
+ var meterBusy = false;
6052
+ var meterQueued = false;
6053
+ var meterClients = /* @__PURE__ */ new Map();
6054
+ var meterFrames = /* @__PURE__ */ new Map();
6055
+ function stopMeters() {
6056
+ meterWorker?.terminate();
6057
+ if (meterUrl) URL.revokeObjectURL(meterUrl);
6058
+ meterWorker = void 0;
6059
+ meterUrl = void 0;
6060
+ meterBusy = false;
6061
+ meterFrames.clear();
6062
+ }
6063
+ function failMeters() {
6064
+ for (const client of [...meterClients.values()]) client.dispose();
6065
+ stopMeters();
6066
+ }
6067
+ function sendMeter(frame) {
6068
+ if (frame) {
6069
+ const pending = meterFrames.get(frame.id);
6070
+ if (pending) Object.assign(pending, frame);
6071
+ else meterFrames.set(frame.id, frame);
6072
+ }
6073
+ if (meterQueued || meterBusy || !meterWorker || !meterFrames.size) return;
6074
+ meterQueued = true;
6075
+ queueMicrotask(() => {
6076
+ meterQueued = false;
6077
+ if (!meterWorker || meterBusy || !meterFrames.size) return;
6078
+ const frames = [...meterFrames.values()];
6079
+ meterFrames.clear();
6080
+ meterBusy = true;
6081
+ try {
6082
+ meterWorker.postMessage({ frames }, frames.flatMap((frame2) => frame2.canvas ? [frame2.canvas] : []));
6083
+ } catch {
6084
+ failMeters();
6085
+ }
6086
+ });
6087
+ }
6088
+ function startMeters() {
6089
+ if (meterWorker) return meterWorker;
6090
+ try {
6091
+ meterUrl = URL.createObjectURL(new Blob([`(${renderMeters.toString()})()`], { type: "text/javascript" }));
6092
+ const worker = new Worker(meterUrl);
6093
+ meterWorker = worker;
6094
+ worker.onerror = () => {
6095
+ if (meterWorker === worker) failMeters();
6096
+ };
6097
+ worker.onmessage = ({ data }) => {
6098
+ if (meterWorker !== worker) return;
6099
+ if (data.ack) meterBusy = false;
6100
+ for (const id of data.failed ?? []) meterClients.get(id)?.dispose();
6101
+ for (const item of data.completed ?? []) meterClients.get(item.id)?.ready(item.revision);
6102
+ sendMeter();
6103
+ };
6104
+ return worker;
6105
+ } catch {
6106
+ stopMeters();
6107
+ return void 0;
6108
+ }
6109
+ }
6110
+ function waveCanvas(shape, initial) {
6111
+ if (typeof Worker === "undefined" || typeof OffscreenCanvas === "undefined" || typeof Path2D === "undefined" || typeof MutationObserver === "undefined" || typeof HTMLCanvasElement === "undefined" || !HTMLCanvasElement.prototype.transferControlToOffscreen) return null;
6112
+ const svg = shape.ownerSVGElement, root = svg?.parentElement;
6113
+ const axis = svg?.querySelector("line");
6114
+ if (!svg || !root || !axis) return null;
6115
+ const canvas = document.createElement("canvas");
6116
+ const worker = startMeters();
6117
+ if (!worker) return null;
6118
+ const id = ++meterSerial;
6119
+ canvas.className = "ad-level-meter-canvas";
6120
+ canvas.setAttribute("aria-hidden", "true");
6121
+ root.append(canvas);
6122
+ const ancestors = [];
6123
+ for (let node = root; node; node = node.parentElement) ancestors.push(node);
6124
+ let disposed = false, enabled = false, queued = false, ready = false, transferred = false, revision = 0;
6125
+ let current = initial, offset = 0, sent = "";
6126
+ const native = () => {
6127
+ root.removeAttribute("data-ad-canvas");
6128
+ canvas.hidden = true;
6129
+ shape.setAttribute("d", current);
6130
+ shape.setAttribute("transform", `translate(${offset} 0)`);
6131
+ };
6132
+ const draw = (d, shift) => {
6133
+ current = d;
6134
+ offset = shift;
6135
+ if (!enabled || disposed) return false;
6136
+ sendMeter({ id, revision, offset, ...sent !== d ? { d } : {} });
6137
+ sent = d;
6138
+ return ready;
6139
+ };
6140
+ function update() {
6141
+ if (disposed) return;
6142
+ const surface = getComputedStyle(svg), wave = getComputedStyle(shape), line2 = getComputedStyle(axis);
6143
+ const host = getComputedStyle(root);
6144
+ const gradient = svg.querySelector("linearGradient");
6145
+ const width = parseFloat(surface.width), height = parseFloat(surface.height);
6146
+ const unsupported = [surface.transform, surface.backgroundImage, wave.stroke, wave.filter, wave.clipPath, wave.maskImage, line2.strokeDasharray].some((value) => value !== "none") || line2.strokeLinecap !== "butt" || wave.fill.startsWith("url(") && (!gradient || wave.fill.match(/#([^"')]+)["']?\)$/)?.[1] !== gradient.id || ["gradientUnits", "gradientTransform", "x1", "x2", "y1", "y2"].some((name) => gradient.hasAttribute(name))) || [surface, host].some((style) => ["Top", "Right", "Bottom", "Left"].some((side) => parseFloat(style.getPropertyValue(`padding-${side.toLowerCase()}`)) > 0 || parseFloat(style.getPropertyValue(`border-${side.toLowerCase()}-width`)) > 0)) || [svg, ...ancestors].some((node) => node.getAnimations?.({ subtree: node === svg }).some((animation) => animation.playState === "running"));
6147
+ enabled = width > 0 && height > 0 && !unsupported;
6148
+ if (!enabled) {
6149
+ native();
6150
+ return;
6151
+ }
6152
+ const ratio = devicePixelRatio || 1;
6153
+ canvas.style.filter = surface.filter;
6154
+ canvas.style.opacity = surface.opacity;
6155
+ canvas.style.backgroundColor = surface.backgroundColor;
6156
+ const stops = wave.fill.startsWith("url(") ? [...svg.querySelectorAll("stop")].map((stop) => {
6157
+ const style = getComputedStyle(stop);
6158
+ return { at: stop.offset.baseVal, color: `color-mix(in srgb, ${style.stopColor} ${Number(style.stopOpacity) * 100}%, transparent)` };
6159
+ }) : [];
6160
+ try {
6161
+ const surface2 = transferred ? void 0 : canvas.transferControlToOffscreen();
6162
+ transferred = true;
6163
+ ready = false;
6164
+ revision++;
6165
+ native();
6166
+ sent = current;
6167
+ sendMeter({ id, revision, d: current, offset, ...surface2 ? { canvas: surface2 } : {}, style: {
6168
+ width: Math.ceil(width * ratio),
6169
+ height: Math.ceil(height * ratio),
6170
+ stroke: line2.stroke,
6171
+ lineWidth: parseFloat(line2.strokeWidth),
6172
+ lineAlpha: Number(line2.opacity) * Number(line2.strokeOpacity),
6173
+ fillAlpha: Number(wave.opacity) * Number(wave.fillOpacity),
6174
+ fill: wave.fill,
6175
+ stops
6176
+ } });
6177
+ } catch {
6178
+ dispose();
6179
+ }
6180
+ }
6181
+ function queue() {
6182
+ if (queued || disposed) return;
6183
+ queued = true;
6184
+ queueMicrotask(() => {
6185
+ queued = false;
6186
+ if (!disposed) update();
6187
+ });
6188
+ }
6189
+ const resized = createResizeObserver(queue);
6190
+ resized.observe(svg);
6191
+ const styles = new MutationObserver(queue);
6192
+ for (const node of ancestors) styles.observe(node, { attributes: true, attributeFilter: ["style", "class", "data-ad-theme", "data-ad-color-scheme"] });
6193
+ styles.observe(svg, { subtree: true, childList: true, attributes: true, attributeFilter: ["style", "class", "fill", "stroke", "stop-color", "stop-opacity", "offset"] });
6194
+ styles.observe(document.head, { subtree: true, childList: true, characterData: true });
6195
+ const transition = (event) => {
6196
+ if (event.target instanceof Element && (event.target.contains(svg) || svg.contains(event.target))) queue();
6197
+ };
6198
+ const events = ["transitionrun", "transitionend", "transitioncancel", "animationstart", "animationend", "animationcancel"];
6199
+ for (const event of events) document.addEventListener(event, transition);
6200
+ window.addEventListener("resize", queue);
6201
+ canvas.addEventListener("contextlost", dispose);
6202
+ function dispose() {
6203
+ if (disposed) return;
6204
+ disposed = true;
6205
+ enabled = false;
6206
+ resized.disconnect();
6207
+ styles.disconnect();
6208
+ window.removeEventListener("resize", queue);
6209
+ canvas.removeEventListener("contextlost", dispose);
6210
+ for (const event of events) document.removeEventListener(event, transition);
6211
+ meterFrames.delete(id);
6212
+ meterClients.delete(id);
6213
+ if (meterWorker === worker) worker?.postMessage({ remove: id });
6214
+ if (!meterClients.size) stopMeters();
6215
+ native();
6216
+ canvas.remove();
6217
+ }
6218
+ meterClients.set(id, { dispose, ready(next) {
6219
+ if (disposed || !enabled || next !== revision || ready) return;
6220
+ ready = true;
6221
+ canvas.hidden = false;
6222
+ root.setAttribute("data-ad-canvas", "");
6223
+ } });
6224
+ update();
6225
+ return { draw, dispose };
6226
+ }
5985
6227
  var loudness = (analyser, buffer) => {
5986
6228
  analyser.getFloatTimeDomainData(buffer);
5987
6229
  let sum = 0;
@@ -6002,15 +6244,16 @@ function LevelMeter({
6002
6244
  const target = useRef27(0);
6003
6245
  const wake = useRef27(() => void 0);
6004
6246
  const level = clamp(value) / 100;
6005
- useEffect13(() => {
6247
+ useLayoutEffect5(() => {
6006
6248
  target.current = active ? level : 0;
6007
6249
  if (active && level > 0) wake.current();
6008
6250
  }, [active, level]);
6009
6251
  useEffect13(() => {
6010
6252
  const shape = path.current;
6011
6253
  if (!shape) return;
6012
- const samples = Array.from({ length: SAMPLES + 1 }, () => 0);
6013
- shape.setAttribute("d", wavePath2(samples));
6254
+ const samples = Array.from({ length: SAMPLES + 1 }, () => waveSample(0));
6255
+ let drawnPath = wavePath2(samples);
6256
+ shape.setAttribute("d", drawnPath);
6014
6257
  wake.current = () => void 0;
6015
6258
  if (!active) return;
6016
6259
  let context;
@@ -6024,6 +6267,8 @@ function LevelMeter({
6024
6267
  source.connect(analyser);
6025
6268
  }
6026
6269
  const buffer = new Float32Array(analyser?.fftSize ?? 0);
6270
+ const painter = waveCanvas(shape, drawnPath);
6271
+ let nativePath = drawnPath;
6027
6272
  let envelope = 0;
6028
6273
  let carry = 0;
6029
6274
  let last = performance.now();
@@ -6032,24 +6277,32 @@ function LevelMeter({
6032
6277
  carry += Math.min(250, now - last);
6033
6278
  last = now;
6034
6279
  const input = analyser ? loudness(analyser, buffer) : target.current;
6280
+ const sampled = carry >= SAMPLE_MS;
6035
6281
  while (carry >= SAMPLE_MS) {
6036
6282
  carry -= SAMPLE_MS;
6037
6283
  envelope += (input - envelope) * (input > envelope ? 0.3 : 0.11);
6038
6284
  if (envelope < 1e-3) envelope = 0;
6039
6285
  samples.shift();
6040
- samples.push(envelope);
6041
- shape.setAttribute("d", wavePath2(samples));
6286
+ samples.push(waveSample(envelope));
6287
+ }
6288
+ if (sampled) {
6289
+ const nextPath = wavePath2(samples);
6290
+ drawnPath = nextPath;
6291
+ }
6292
+ const quiet = !analyser && input === 0 && envelope === 0 && samples.every((sample) => sample.level === 0);
6293
+ const shift = quiet ? 0 : Number((-(carry / SAMPLE_MS) * STEP).toFixed(3));
6294
+ if (!painter?.draw(drawnPath, shift)) {
6295
+ if (nativePath !== drawnPath) {
6296
+ shape.setAttribute("d", drawnPath);
6297
+ nativePath = drawnPath;
6298
+ }
6299
+ shape.setAttribute("transform", `translate(${shift} 0)`);
6042
6300
  }
6043
- if (!analyser && input === 0 && envelope === 0 && samples.every((sample) => sample === 0)) {
6044
- shape.setAttribute("transform", "translate(0 0)");
6301
+ if (quiet) {
6045
6302
  stop?.();
6046
6303
  stop = void 0;
6047
6304
  return;
6048
6305
  }
6049
- shape.setAttribute(
6050
- "transform",
6051
- `translate(${(-(carry / SAMPLE_MS) * STEP).toFixed(3)} 0)`
6052
- );
6053
6306
  };
6054
6307
  wake.current = () => {
6055
6308
  if (stop) return;
@@ -6061,10 +6314,35 @@ function LevelMeter({
6061
6314
  return () => {
6062
6315
  wake.current = () => void 0;
6063
6316
  stop?.();
6317
+ painter?.dispose();
6064
6318
  source?.disconnect();
6065
6319
  void context?.close();
6066
6320
  };
6067
6321
  }, [active, stream]);
6322
+ const waveform = useMemo5(() => /* @__PURE__ */ jsxs39(
6323
+ "svg",
6324
+ {
6325
+ viewBox: `0 0 ${WIDTH} ${HEIGHT}`,
6326
+ preserveAspectRatio: "none",
6327
+ "aria-hidden": "true",
6328
+ children: [
6329
+ /* @__PURE__ */ jsx48("defs", { children: /* @__PURE__ */ jsxs39("linearGradient", { id: `${id}-wave`, children: [
6330
+ /* @__PURE__ */ jsx48("stop", { stopColor: "var(--ad-primary-700)" }),
6331
+ /* @__PURE__ */ jsx48("stop", { offset: "0.52", stopColor: "var(--ad-red)" }),
6332
+ /* @__PURE__ */ jsx48("stop", { offset: "1", stopColor: "var(--ad-secondary-100)" })
6333
+ ] }) }),
6334
+ /* @__PURE__ */ jsx48("line", { className: "ad-level-meter-axis", x2: WIDTH, y1: MID2, y2: MID2 }),
6335
+ /* @__PURE__ */ jsx48(
6336
+ "path",
6337
+ {
6338
+ ref: path,
6339
+ className: "ad-level-meter-wave",
6340
+ fill: `url(#${id}-wave)`
6341
+ }
6342
+ )
6343
+ ]
6344
+ }
6345
+ ), [id]);
6068
6346
  return /* @__PURE__ */ jsx48(
6069
6347
  "div",
6070
6348
  {
@@ -6076,30 +6354,7 @@ function LevelMeter({
6076
6354
  "aria-valuenow": stream ? void 0 : Math.round(active ? level * 100 : 0),
6077
6355
  "data-active": active,
6078
6356
  "data-compact": compact || void 0,
6079
- children: /* @__PURE__ */ jsxs39(
6080
- "svg",
6081
- {
6082
- viewBox: `0 0 ${WIDTH} ${HEIGHT}`,
6083
- preserveAspectRatio: "none",
6084
- "aria-hidden": "true",
6085
- children: [
6086
- /* @__PURE__ */ jsx48("defs", { children: /* @__PURE__ */ jsxs39("linearGradient", { id: `${id}-wave`, children: [
6087
- /* @__PURE__ */ jsx48("stop", { stopColor: "var(--ad-primary-700)" }),
6088
- /* @__PURE__ */ jsx48("stop", { offset: "0.52", stopColor: "var(--ad-red)" }),
6089
- /* @__PURE__ */ jsx48("stop", { offset: "1", stopColor: "var(--ad-secondary-100)" })
6090
- ] }) }),
6091
- /* @__PURE__ */ jsx48("line", { className: "ad-level-meter-axis", x2: WIDTH, y1: MID2, y2: MID2 }),
6092
- /* @__PURE__ */ jsx48(
6093
- "path",
6094
- {
6095
- ref: path,
6096
- className: "ad-level-meter-wave",
6097
- fill: `url(#${id}-wave)`
6098
- }
6099
- )
6100
- ]
6101
- }
6102
- )
6357
+ children: waveform
6103
6358
  }
6104
6359
  );
6105
6360
  }
@@ -6147,7 +6402,7 @@ var KaraokeLyrics = ({
6147
6402
  );
6148
6403
 
6149
6404
  // src/components/media/MelodyRoll/MelodyRoll.tsx
6150
- import { useEffect as useEffect14, useLayoutEffect as useLayoutEffect5, useMemo as useMemo5, useRef as useRef28, useState as useState13 } from "react";
6405
+ import { useEffect as useEffect14, useLayoutEffect as useLayoutEffect6, useMemo as useMemo6, useRef as useRef28, useState as useState13 } from "react";
6151
6406
 
6152
6407
  // src/components/media/MelodyRoll/melody-glow.js
6153
6408
  function renderGlow() {
@@ -7254,7 +7509,7 @@ var MelodyRoll = ({
7254
7509
  const glow3 = useRef28(null);
7255
7510
  const [glowReady, setGlowReady] = useState13(false);
7256
7511
  const [span, setSpan] = useControllable(windowProp, defaultWindow, onWindowChange);
7257
- const indexed = useMemo5(() => {
7512
+ const indexed = useMemo6(() => {
7258
7513
  let low2 = 60, high2 = 64;
7259
7514
  const blocks = [];
7260
7515
  for (let i = 0; i < notes.length; i += 128) {
@@ -7278,7 +7533,7 @@ var MelodyRoll = ({
7278
7533
  const x = (seconds) => (seconds - from) / span * 100;
7279
7534
  const rangeStart = glowReady ? origin : from;
7280
7535
  const rangeEnd = glowReady ? origin + span * 2 : from + span;
7281
- const visible = useMemo5(() => indexed.blocks.filter((block) => block.end >= rangeStart && block.start <= rangeEnd).flatMap((block) => block.notes.filter((note) => note.end >= rangeStart && note.start <= rangeEnd)), [indexed, rangeStart, rangeEnd]);
7536
+ const visible = useMemo6(() => indexed.blocks.filter((block) => block.end >= rangeStart && block.start <= rangeEnd).flatMap((block) => block.notes.filter((note) => note.end >= rangeStart && note.start <= rangeEnd)), [indexed, rangeStart, rangeEnd]);
7282
7537
  const phases = glowReady ? "" : visible.map((note) => note.end < from || note.start > from + span ? "x" : position >= note.start && position <= note.end ? "n" : position > note.end ? "p" : "-").join("");
7283
7538
  const hits = hitIds?.size ? visible.map((note) => hitIds.has(note.id) ? "h" : "-").join("") : "";
7284
7539
  const glowState = { beat: clamp(beat, 0, 1), pitch: livePitch, high, rows, lead, accuracy: clamp(accuracy, 0, 1), level: clamp(level, 0, 1), hit, position, span, notes: visible, hits, showLyrics };
@@ -7290,11 +7545,11 @@ var MelodyRoll = ({
7290
7545
  glow3.current = null;
7291
7546
  };
7292
7547
  }, []);
7293
- useLayoutEffect5(() => {
7548
+ useLayoutEffect6(() => {
7294
7549
  glow3.current?.update(glowState);
7295
7550
  }, [beat, livePitch, high, rows, lead, accuracy, level, hit, position, span, visible, hits, showLyrics]);
7296
7551
  const nativeHits = glowReady ? "" : hits;
7297
- const noteElements = useMemo5(() => glowReady ? void 0 : visible.map((note, i) => {
7552
+ const noteElements = useMemo6(() => glowReady ? void 0 : visible.map((note, i) => {
7298
7553
  const hidden = phases[i] === "x";
7299
7554
  return /* @__PURE__ */ jsx50(
7300
7555
  "span",
@@ -7334,7 +7589,7 @@ var MelodyRoll = ({
7334
7589
  setSpan(clamp(span * (event.deltaY > 0 ? 1.12 : 1 / 1.12), 2, 20));
7335
7590
  };
7336
7591
  const nativeState = glowReady ? null : glowState;
7337
- const content = useMemo5(() => /* @__PURE__ */ jsxs41("div", { className: "ad-melody-roll-content", children: [
7592
+ const content = useMemo6(() => /* @__PURE__ */ jsxs41("div", { className: "ad-melody-roll-content", children: [
7338
7593
  /* @__PURE__ */ jsx50(
7339
7594
  PianoKeyboard,
7340
7595
  {
@@ -7392,7 +7647,7 @@ var MelodyRoll = ({
7392
7647
  import { useRef as useRef30 } from "react";
7393
7648
 
7394
7649
  // src/components/layout/FloatingPanel/useFloatingPanel.ts
7395
- import { useCallback as useCallback2, useEffect as useEffect15, useLayoutEffect as useLayoutEffect6, useRef as useRef29, useState as useState14 } from "react";
7650
+ import { useCallback as useCallback2, useEffect as useEffect15, useLayoutEffect as useLayoutEffect7, useRef as useRef29, useState as useState14 } from "react";
7396
7651
  var resizeEdges = ["n", "s", "e", "w", "ne", "nw", "se", "sw"];
7397
7652
  var threshold = 3;
7398
7653
  var panelControls = "button, a, input, select, textarea, [role=slider], [role=button], [role=switch], [role=listbox], [contenteditable=true], .ad-rotary-knob";
@@ -7404,7 +7659,7 @@ var useFloatingPanel = (frameRef, options = {}) => {
7404
7659
  const latest = useRef29(options);
7405
7660
  latest.current = options;
7406
7661
  const saved = options.layout;
7407
- useLayoutEffect6(() => {
7662
+ useLayoutEffect7(() => {
7408
7663
  drag.current = null;
7409
7664
  setLive(null);
7410
7665
  }, [saved?.left, saved?.top, saved?.width, saved?.height]);
@@ -7566,7 +7821,7 @@ var FloatingPanel = ({ layout: saved, onLayoutChange, defaultSize, limits, onDra
7566
7821
  };
7567
7822
 
7568
7823
  // src/components/media/Sparkline/Sparkline.tsx
7569
- import { useMemo as useMemo6, useRef as useRef31 } from "react";
7824
+ import { useMemo as useMemo7, useRef as useRef31 } from "react";
7570
7825
  import { Fragment as Fragment5, jsx as jsx52, jsxs as jsxs43 } from "react/jsx-runtime";
7571
7826
  var DEMO3 = [12, 23, 17, 31, 43, 24, 28, 20, 41, 29, 51, 34, 38, 22, 31, 16, 23];
7572
7827
  var Sparkline = (p) => {
@@ -7574,7 +7829,7 @@ var Sparkline = (p) => {
7574
7829
  const id = useSvgId();
7575
7830
  const ref = useRef31(null);
7576
7831
  usePauseOffscreen(ref);
7577
- const { line: line2, lastX, lastY } = useMemo6(() => {
7832
+ const { line: line2, lastX, lastY } = useMemo7(() => {
7578
7833
  if (!values.length) return { line: "", lastX: 0, lastY: 0 };
7579
7834
  let min = p.fit ? Infinity : 0;
7580
7835
  let max = p.fit ? -Infinity : 1;
@@ -7646,7 +7901,7 @@ import { useRef as useRef32 } from "react";
7646
7901
  import { jsx as jsx53, jsxs as jsxs44 } from "react/jsx-runtime";
7647
7902
  var POINTS = 23;
7648
7903
  var STEP2 = 65 / (POINTS - 1);
7649
- var X = Array.from({ length: POINTS }, (_, k) => k * STEP2 / 65 * 600);
7904
+ var X2 = Array.from({ length: POINTS }, (_, k) => k * STEP2 / 65 * 600);
7650
7905
  var strand = (j, t) => {
7651
7906
  const ys = Array.from({ length: POINTS + 2 }, (_, k) => {
7652
7907
  const i = (k - 1) * STEP2;
@@ -7655,7 +7910,7 @@ var strand = (j, t) => {
7655
7910
  let d = `M0 ${ys[1].toFixed(2)}`;
7656
7911
  for (let k = 1; k < POINTS; k += 1) {
7657
7912
  const y0 = ys[k - 1], y1 = ys[k], y2 = ys[k + 1], y3 = ys[k + 2];
7658
- const x1 = X[k - 1], x2 = X[k], third = (x2 - x1) / 3;
7913
+ const x1 = X2[k - 1], x2 = X2[k], third = (x2 - x1) / 3;
7659
7914
  d += `C${(x1 + third).toFixed(2)} ${(y1 + (y2 - y0) / 6).toFixed(2)} ${(x2 - third).toFixed(2)} ${(y2 - (y3 - y1) / 6).toFixed(2)} ${x2.toFixed(2)} ${y2.toFixed(2)}`;
7660
7915
  }
7661
7916
  return d;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@neo-ui-kit/react",
3
- "version": "1.3.13",
3
+ "version": "1.3.15",
4
4
  "description": "NEO UI — React component library",
5
5
  "type": "module",
6
6
  "license": "MIT",