@neo-ui-kit/react 1.3.11 → 1.3.13
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 +10 -1
- package/dist/index.js +100 -6
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -4,6 +4,15 @@ 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.13
|
|
8
|
+
|
|
9
|
+
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.
|
|
10
|
+
|
|
11
|
+
## 1.3.12
|
|
12
|
+
|
|
13
|
+
- MelodyRoll avoids repeated full style measurements for unrelated animated CSS variables and native root shadows while its beat is idle. Theme aliases still trigger reconfiguration.
|
|
14
|
+
- Live-pitch measurements reached 59.7/59.0 main-thread FPS at CPU ×8/×16 in the library fixture. A karaoke-style animated native shadow still limits ×16 page updates; stable 60 FPS is not established for that scenario. See the [performance report](https://github.com/Pro100Dimka/adui/blob/main/evidence/performance-report.md).
|
|
15
|
+
|
|
7
16
|
## 1.3.11
|
|
8
17
|
|
|
9
18
|
LevelMeter resumes its waveform when a signal arrives after silence, including when the first animation frame precedes the next sample.
|
|
@@ -42,7 +51,7 @@ import "@neo-ui-kit/react/styles.css";
|
|
|
42
51
|
To test repository changes before publishing, run `npm run package` from the repository root and install the resulting archive in a separate application:
|
|
43
52
|
|
|
44
53
|
```bash
|
|
45
|
-
npm install /path/to/adui/release/neo-ui-kit-react-1.3.
|
|
54
|
+
npm install /path/to/adui/release/neo-ui-kit-react-1.3.13.tgz
|
|
46
55
|
```
|
|
47
56
|
|
|
48
57
|
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-пакет).
|
package/dist/index.js
CHANGED
|
@@ -6327,6 +6327,7 @@ function renderGlow() {
|
|
|
6327
6327
|
scene.noteStates = kept;
|
|
6328
6328
|
scene.keyStates ??= /* @__PURE__ */ new Map();
|
|
6329
6329
|
const keyStates = /* @__PURE__ */ new Map();
|
|
6330
|
+
let keysUntil = 0;
|
|
6330
6331
|
for (const key of style.keys) {
|
|
6331
6332
|
const previous = scene.keyStates.get(key.midi);
|
|
6332
6333
|
const active = state.pitch !== void 0 && key.midi === Math.round(state.pitch);
|
|
@@ -6334,6 +6335,7 @@ function renderGlow() {
|
|
|
6334
6335
|
const pressed = scene.pressed?.includes(key.midi);
|
|
6335
6336
|
const variant = hit ? "hit" : active ? "active" : pressed ? "pressed" : "base";
|
|
6336
6337
|
const target = key[variant];
|
|
6338
|
+
if (previous?.variant !== variant) scene.keysDirty = true;
|
|
6337
6339
|
const item = {
|
|
6338
6340
|
key,
|
|
6339
6341
|
target,
|
|
@@ -6346,11 +6348,12 @@ function renderGlow() {
|
|
|
6346
6348
|
hitStart: hit && !previous?.hit ? previous ? now : scene.keyTimes?.[key.midi] ?? now : previous?.hitStart ?? now
|
|
6347
6349
|
};
|
|
6348
6350
|
keyStates.set(key.midi, item);
|
|
6349
|
-
|
|
6351
|
+
keysUntil = Math.max(keysUntil, ...["color", "shadows", "shift"].map((name) => item[name].start + item[name].duration), hit && key.hitDuration > 0 ? item.hitStart + key.hitDuration * key.hitIterations : 0);
|
|
6350
6352
|
}
|
|
6351
6353
|
scene.keyStates = keyStates;
|
|
6352
6354
|
scene.noteTimes = scene.keyTimes = void 0;
|
|
6353
|
-
scene.
|
|
6355
|
+
scene.keysUntil = keysUntil;
|
|
6356
|
+
scene.foregroundUntil = Math.max(until, keysUntil);
|
|
6354
6357
|
}
|
|
6355
6358
|
function drawForeground(scene, now) {
|
|
6356
6359
|
if (!scene.foreground) return;
|
|
@@ -6434,6 +6437,10 @@ function renderGlow() {
|
|
|
6434
6437
|
ctx.drawImage(painted, x - padding / dpr, top + height / 2 + (y - padding / dpr - top - height / 2) * scale, painted.width / dpr, painted.height / dpr * scale);
|
|
6435
6438
|
ctx.restore();
|
|
6436
6439
|
}
|
|
6440
|
+
const keysAnimating = now < scene.keysUntil;
|
|
6441
|
+
if (!scene.keysDirty && !scene.keysAnimating && !keysAnimating) return;
|
|
6442
|
+
scene.keysDirty = false;
|
|
6443
|
+
scene.keysAnimating = keysAnimating;
|
|
6437
6444
|
keyCtx.clearRect(0, 0, style.keyWidth, style.keyHeight);
|
|
6438
6445
|
scene.keySprites ??= /* @__PURE__ */ new Map();
|
|
6439
6446
|
for (const item of scene.keyStates.values()) {
|
|
@@ -6489,7 +6496,11 @@ function renderGlow() {
|
|
|
6489
6496
|
}
|
|
6490
6497
|
function drawVoice(scene, now) {
|
|
6491
6498
|
const { voiceCtx: ctx, laneWidth, laneHeight, state, voiceStyle: style, dpr } = scene;
|
|
6499
|
+
const active = state.pitch !== void 0 || state.beat > 0;
|
|
6500
|
+
if (!active && !scene.voicePainted) return;
|
|
6501
|
+
scene.voicePainted = active;
|
|
6492
6502
|
ctx.clearRect(0, 0, laneWidth, laneHeight);
|
|
6503
|
+
if (!active) return;
|
|
6493
6504
|
ctx.save();
|
|
6494
6505
|
const head = state.lead * laneWidth - scene.headWidth / 2;
|
|
6495
6506
|
const offset = laneWidth + 200;
|
|
@@ -6541,7 +6552,11 @@ function renderGlow() {
|
|
|
6541
6552
|
}
|
|
6542
6553
|
function drawTrail(scene) {
|
|
6543
6554
|
const { trailCtx: ctx, laneWidth, laneHeight, state, trailStyle } = scene;
|
|
6555
|
+
const active = scene.trail.some((point) => point.pitch !== void 0);
|
|
6556
|
+
if (!active && !scene.trailPainted) return;
|
|
6557
|
+
scene.trailPainted = active;
|
|
6544
6558
|
ctx.clearRect(0, 0, laneWidth, laneHeight);
|
|
6559
|
+
if (!active) return;
|
|
6545
6560
|
const paths = [];
|
|
6546
6561
|
let path = null;
|
|
6547
6562
|
for (const point of scene.trail) {
|
|
@@ -6568,8 +6583,9 @@ function renderGlow() {
|
|
|
6568
6583
|
}
|
|
6569
6584
|
function draw(scene, now) {
|
|
6570
6585
|
const { ctx, width, height, pad, radius: radius2, blur, color: color3, dpr } = scene;
|
|
6571
|
-
ctx.clearRect(0, 0, width + pad * 2, height + pad * 2);
|
|
6572
6586
|
const amount = value(scene, now);
|
|
6587
|
+
if (amount > 0 || scene.glowPainted) ctx.clearRect(0, 0, width + pad * 2, height + pad * 2);
|
|
6588
|
+
scene.glowPainted = amount > 0;
|
|
6573
6589
|
if (amount > 0) {
|
|
6574
6590
|
ctx.save();
|
|
6575
6591
|
ctx.beginPath();
|
|
@@ -6661,6 +6677,7 @@ function renderGlow() {
|
|
|
6661
6677
|
scene.keyCtx = scene.keyCanvas.getContext("2d");
|
|
6662
6678
|
scene.noteCtx.setTransform(data.dpr, 0, 0, data.dpr, 0, 0);
|
|
6663
6679
|
scene.keyCtx.setTransform(data.dpr, 0, 0, data.dpr, 0, 0);
|
|
6680
|
+
scene.keysDirty = true;
|
|
6664
6681
|
scene.sprites?.clear();
|
|
6665
6682
|
scene.keySprites?.clear();
|
|
6666
6683
|
scene.filteredSprites?.clear();
|
|
@@ -6699,6 +6716,7 @@ function renderGlow() {
|
|
|
6699
6716
|
retargetVoice(scene, now, previous);
|
|
6700
6717
|
retargetForeground(scene, now);
|
|
6701
6718
|
} else if (scene && data.type === "keys") {
|
|
6719
|
+
scene.keysDirty = true;
|
|
6702
6720
|
scene.pressed = data.pressed;
|
|
6703
6721
|
scene.hovered = data.hovered;
|
|
6704
6722
|
retargetForeground(scene, now);
|
|
@@ -6757,18 +6775,88 @@ function createMelodyGlow(root, initialState, onReady) {
|
|
|
6757
6775
|
for (const layer of [canvas, voiceCanvas, trailCanvas, noteCanvas, keyCanvas]) layer.setAttribute("aria-hidden", "true");
|
|
6758
6776
|
let state = initialState, disposed = false, transferred = false, inView = true, revision = 0, signature = "";
|
|
6759
6777
|
const visible = () => inView && !document.hidden && !root.closest("[data-ad-paused], [data-ad-offscreen]");
|
|
6760
|
-
|
|
6778
|
+
let lastVisible;
|
|
6779
|
+
const visibility = () => {
|
|
6780
|
+
const next = visible();
|
|
6781
|
+
if (next === lastVisible) return;
|
|
6782
|
+
lastVisible = next;
|
|
6783
|
+
worker.postMessage({ type: "visible", id, visible: next });
|
|
6784
|
+
};
|
|
6761
6785
|
const resize = new ResizeObserver(configure);
|
|
6762
6786
|
const intersection = new IntersectionObserver(([entry]) => {
|
|
6763
6787
|
inView = entry.isIntersecting;
|
|
6764
6788
|
visibility();
|
|
6765
6789
|
});
|
|
6790
|
+
let variableUses, variableSheets = [], nativeShadowChanged = false;
|
|
6791
|
+
const readVariableUses = () => {
|
|
6792
|
+
const uses = /* @__PURE__ */ new Map();
|
|
6793
|
+
const declarations = (style, selector) => {
|
|
6794
|
+
for (const property of style) for (const [, variable] of style.getPropertyValue(property).matchAll(/var\(\s*(--[\w-]+)/g)) {
|
|
6795
|
+
if (!uses.has(variable)) uses.set(variable, []);
|
|
6796
|
+
uses.get(variable).push({ property, selector });
|
|
6797
|
+
}
|
|
6798
|
+
};
|
|
6799
|
+
const visit = (rules) => {
|
|
6800
|
+
for (const rule of rules) {
|
|
6801
|
+
if (rule.selectorText?.includes("[style") || rule.conditionText?.includes("style(")) throw new Error("Style-dependent selectors require full measurement");
|
|
6802
|
+
if (rule.style) declarations(rule.style, rule.selectorText);
|
|
6803
|
+
if (rule.styleSheet) visit(rule.styleSheet.cssRules);
|
|
6804
|
+
else if (rule.cssRules) visit(rule.cssRules);
|
|
6805
|
+
}
|
|
6806
|
+
};
|
|
6807
|
+
variableSheets = [...document.styleSheets, ...document.adoptedStyleSheets ?? []];
|
|
6808
|
+
for (const sheet of variableSheets) visit(sheet.cssRules);
|
|
6809
|
+
for (let node = root; node; node = node.parentElement) declarations(node.style, node === root ? null : void 0);
|
|
6810
|
+
return uses;
|
|
6811
|
+
};
|
|
6812
|
+
const affectsRendering = (records) => {
|
|
6813
|
+
const changed = /* @__PURE__ */ new Set();
|
|
6814
|
+
for (const record of records) {
|
|
6815
|
+
if (["data-ad-paused", "data-ad-offscreen"].includes(record.attributeName)) continue;
|
|
6816
|
+
if (record.attributeName !== "style") return true;
|
|
6817
|
+
const old = document.createElement("div").style;
|
|
6818
|
+
old.cssText = record.oldValue ?? "";
|
|
6819
|
+
const current = record.target.style;
|
|
6820
|
+
for (const property of /* @__PURE__ */ new Set([...old, ...current])) {
|
|
6821
|
+
if (old.getPropertyValue(property) === current.getPropertyValue(property) && old.getPropertyPriority(property) === current.getPropertyPriority(property)) continue;
|
|
6822
|
+
if (["opacity", "transform", "translate", "rotate", "scale", "z-index"].includes(property)) continue;
|
|
6823
|
+
if (!property.startsWith("--")) return true;
|
|
6824
|
+
changed.add(property);
|
|
6825
|
+
if (current.getPropertyValue(property).includes("var(")) variableUses = void 0;
|
|
6826
|
+
}
|
|
6827
|
+
}
|
|
6828
|
+
if (!changed.size) return false;
|
|
6829
|
+
try {
|
|
6830
|
+
const sheets = [...document.styleSheets, ...document.adoptedStyleSheets ?? []];
|
|
6831
|
+
if (sheets.length !== variableSheets.length || sheets.some((sheet, i) => sheet !== variableSheets[i])) variableUses = void 0;
|
|
6832
|
+
variableUses ??= readVariableUses();
|
|
6833
|
+
for (const variable of changed) for (const use of variableUses.get(variable) ?? []) {
|
|
6834
|
+
if (use.property.startsWith("--")) {
|
|
6835
|
+
changed.add(use.property);
|
|
6836
|
+
continue;
|
|
6837
|
+
}
|
|
6838
|
+
if (use.property === "box-shadow" && state.beat === 0 && (use.selector === null || use.selector && !use.selector.includes(",") && root.matches(use.selector) && !root.querySelector(use.selector))) {
|
|
6839
|
+
nativeShadowChanged = true;
|
|
6840
|
+
continue;
|
|
6841
|
+
}
|
|
6842
|
+
return true;
|
|
6843
|
+
}
|
|
6844
|
+
return false;
|
|
6845
|
+
} catch {
|
|
6846
|
+
return true;
|
|
6847
|
+
}
|
|
6848
|
+
};
|
|
6766
6849
|
const tokens = new MutationObserver((records) => {
|
|
6767
6850
|
visibility();
|
|
6768
|
-
if (records
|
|
6851
|
+
if (affectsRendering(records)) configure();
|
|
6769
6852
|
});
|
|
6853
|
+
const stylesheets = new MutationObserver((records) => {
|
|
6854
|
+
variableUses = void 0;
|
|
6855
|
+
if (records.some((record) => record.target.nodeName === "STYLE" || record.target.parentElement?.nodeName === "STYLE" || [...record.addedNodes, ...record.removedNodes].some((node) => node.nodeType === 1 && node.matches('style,link[rel="stylesheet"]')))) configure();
|
|
6856
|
+
});
|
|
6857
|
+
if (document.head) stylesheets.observe(document.head, { childList: true, subtree: true, characterData: true });
|
|
6770
6858
|
for (let node = root; node; node = node.parentElement)
|
|
6771
|
-
tokens.observe(node, { attributes: true, attributeFilter: ["style", "class", "data-ad-theme", "data-ad-color-scheme", "data-ad-motion", "data-ad-paused", "data-ad-offscreen"] });
|
|
6859
|
+
tokens.observe(node, { attributes: true, attributeOldValue: true, attributeFilter: ["style", "class", "data-ad-theme", "data-ad-color-scheme", "data-ad-motion", "data-ad-paused", "data-ad-offscreen"] });
|
|
6772
6860
|
resize.observe(root);
|
|
6773
6861
|
resize.observe(lane);
|
|
6774
6862
|
intersection.observe(root);
|
|
@@ -6866,6 +6954,7 @@ function createMelodyGlow(root, initialState, onReady) {
|
|
|
6866
6954
|
};
|
|
6867
6955
|
function configure() {
|
|
6868
6956
|
if (disposed || !root.clientWidth || !root.clientHeight) return;
|
|
6957
|
+
variableUses = void 0;
|
|
6869
6958
|
const temporary = [];
|
|
6870
6959
|
try {
|
|
6871
6960
|
const probe = document.createElement("div");
|
|
@@ -7074,6 +7163,7 @@ function createMelodyGlow(root, initialState, onReady) {
|
|
|
7074
7163
|
resize.disconnect();
|
|
7075
7164
|
intersection.disconnect();
|
|
7076
7165
|
tokens.disconnect();
|
|
7166
|
+
stylesheets.disconnect();
|
|
7077
7167
|
keyObserver.disconnect();
|
|
7078
7168
|
keyboard.removeEventListener("pointerover", keysChanged);
|
|
7079
7169
|
keyboard.removeEventListener("pointerout", keysChanged);
|
|
@@ -7104,6 +7194,10 @@ function createMelodyGlow(root, initialState, onReady) {
|
|
|
7104
7194
|
if (next.notes === state.notes) delete sent.notes;
|
|
7105
7195
|
if (next.hits === state.hits) delete sent.hits;
|
|
7106
7196
|
state = next;
|
|
7197
|
+
if (nativeShadowChanged && next.beat > 0) {
|
|
7198
|
+
nativeShadowChanged = false;
|
|
7199
|
+
configure();
|
|
7200
|
+
}
|
|
7107
7201
|
worker.postMessage({ type: "state", id, state: sent, time: performance.timeOrigin + performance.now() });
|
|
7108
7202
|
}
|
|
7109
7203
|
},
|