@iasiv5/dsh-skins 1.5.19 → 1.5.20

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.
Files changed (2) hide show
  1. package/lib/client.js +67 -3
  2. package/package.json +2 -2
package/lib/client.js CHANGED
@@ -413,6 +413,9 @@
413
413
  const [r, g, b, a] = channels;
414
414
  return { r, g, b, a: Math.min(1, Math.max(0, a)) };
415
415
  }
416
+ function sidebarPaintOrderCss(bodyAttribute) {
417
+ return `body[${datasetAttribute(bodyAttribute)}] [class*="_sidebarCol"]{position:relative;z-index:1}`;
418
+ }
416
419
  function buildShellAlignmentCss(bodyAttribute, opaqueFills, backdrop) {
417
420
  const body = `body[${datasetAttribute(bodyAttribute)}]`;
418
421
  const scoped = `html[data-windows-titlebar] ${body}`;
@@ -450,10 +453,12 @@
450
453
  if (opaqueFills === null) return "";
451
454
  const body = `body[${datasetAttribute(bodyAttribute)}]`;
452
455
  const column = ' [data-rightbar-fullscreen] [class*="_rightbarCol"]';
453
- const dockPanel = ' [data-sidebar-right-panel="fullscreen"]';
454
- const dockPane = ' [data-sidebar-right-panel="fullscreen"] [data-dockkit-pane]';
456
+ const emptyPaneGuard = ':not(:has([class*="_paneBody"] > [class*="_empty"]))';
457
+ const openGate = '[data-sidebar-right-open="true"]';
458
+ const dockPanel = ` [data-sidebar-right-panel="fullscreen"]${openGate}${emptyPaneGuard}`;
459
+ const dockPane = ` [data-sidebar-right-panel="fullscreen"]${openGate} [data-dockkit-pane]${emptyPaneGuard}`;
455
460
  return [
456
- "/* dsh-skins fullscreen base (ADR-0020): solid skin-tone base while data-rightbar-fullscreen is set — the fullscreen dockkit PANE paints a translucent bg token where the official theme paints opaque white (DOM-dump evidence), so the pane is the bleed surface; solid matches the official look. Docked column untouched */",
461
+ "/* dsh-skins fullscreen base (ADR-0020): solid skin-tone base while data-rightbar-fullscreen is set — the fullscreen dockkit PANE paints a translucent bg token where the official theme paints opaque white (DOM-dump evidence), so the pane is the bleed surface; solid matches the official look. Docked column untouched. Sheet rules gated on data-sidebar-right-open (stale-fullscreen collapse, ADR-0021 amendment 四) */",
457
462
  `${body}${column},${body}${dockPanel},${body}${dockPane}{background-color:${opaqueFills.light}!important}`,
458
463
  `${body}[data-ds-dark-theme]${column},${body}[data-ds-dark-theme]${dockPanel},${body}[data-ds-dark-theme]${dockPane}{background-color:${opaqueFills.dark}!important}`
459
464
  ].join("\n");
@@ -638,6 +643,54 @@ ${darkSelector}{${dark.join(";")}}`;
638
643
  return () => {
639
644
  };
640
645
  }
646
+ function refreshSkinStyleTags() {
647
+ if (typeof requestAnimationFrame !== "function" || typeof document === "undefined" || document.head?.querySelectorAll === void 0) return;
648
+ const tags = [...document.head.querySelectorAll('style[data-plugin-css^="dsh-skins/"]')];
649
+ if (tags.length === 0) return;
650
+ const saved = tags.map((tag) => ({ id: tag.dataset.pluginCss, content: tag.textContent }));
651
+ for (const tag of tags) tag.remove();
652
+ requestAnimationFrame(() => requestAnimationFrame(() => {
653
+ for (const { id, content } of saved) {
654
+ if (document.querySelector(`style[data-plugin-css="${id}"]`) !== null) continue;
655
+ const fresh = document.createElement("style");
656
+ fresh.dataset.plugin = "dsh-skins";
657
+ fresh.dataset.pluginCss = id;
658
+ fresh.textContent = content;
659
+ document.head.append(fresh);
660
+ }
661
+ }));
662
+ }
663
+ let rightPanelRepaintHookInstalled = false;
664
+ function installRightPanelRepaintHook() {
665
+ if (rightPanelRepaintHookInstalled || typeof MutationObserver !== "function") return;
666
+ if (typeof document === "undefined" || document.body === void 0 || document.body === null) return;
667
+ rightPanelRepaintHookInstalled = true;
668
+ const observer = new MutationObserver((records) => {
669
+ for (const record of records ?? []) {
670
+ const before = record.oldValue;
671
+ const after = record.target.getAttribute?.("data-sidebar-right-panel");
672
+ if (before !== "fullscreen" || after === "fullscreen") continue;
673
+ const tag = document.querySelector('style[data-plugin-css$=".fullscreen.css"]');
674
+ if (tag === null) continue;
675
+ const id = tag.dataset.pluginCss;
676
+ const content = tag.textContent;
677
+ tag.remove();
678
+ requestAnimationFrame(() => requestAnimationFrame(() => {
679
+ const fresh = document.createElement("style");
680
+ fresh.dataset.plugin = "dsh-skins";
681
+ fresh.dataset.pluginCss = id;
682
+ fresh.textContent = content;
683
+ document.head.append(fresh);
684
+ }));
685
+ }
686
+ });
687
+ observer.observe(document.body, {
688
+ subtree: true,
689
+ attributes: true,
690
+ attributeFilter: ["data-sidebar-right-panel"],
691
+ attributeOldValue: true
692
+ });
693
+ }
641
694
  function buildEffects(skin, effects) {
642
695
  if (!ctx) throw new Error("[dsh-skins] mount before apply");
643
696
  const body = document.body;
@@ -688,6 +741,13 @@ ${darkSelector}{${dark.join(";")}}`;
688
741
  const tag = styleTag(`dsh-skins/${skin.id}.css`, effects.staticCss);
689
742
  stops.push(() => tag.remove());
690
743
  }
744
+ {
745
+ const tag = styleTag(
746
+ `dsh-skins/${skin.id}.paintorder.css`,
747
+ sidebarPaintOrderCss(effects.bodyAttribute)
748
+ );
749
+ stops.push(() => tag.remove());
750
+ }
691
751
  {
692
752
  const fills = effects.tokenOverrides?.["--dsw-specific-sidebar-fill"] ?? null;
693
753
  const lightFill = fills === null ? null : parseRgbaChannels(fills.light);
@@ -801,6 +861,8 @@ ${darkSelector}{${dark.join(";")}}`;
801
861
  mounted = { skin, effects, overrides, stop };
802
862
  selectedId = skin.id;
803
863
  announce(skin.id);
864
+ installRightPanelRepaintHook();
865
+ if (previous !== null) refreshSkinStyleTags();
804
866
  return true;
805
867
  }
806
868
  function restorePrevious(previous, cause) {
@@ -2024,6 +2086,8 @@ ${references.join("\n")}`,
2024
2086
  type: "button",
2025
2087
  role: "menuitemradio",
2026
2088
  "aria-checked": activeId === skin.id,
2089
+ "data-skin-id": skin.id,
2090
+ // ADR-0021/R21: stable hook for tests and headless verification — never match on localized labels
2027
2091
  className: `dsh-skins-pop-card${activeId === skin.id ? " dsh-skins-pop-card-on" : ""}`,
2028
2092
  onClick: () => {
2029
2093
  if (personalizeId !== null && personalizeId !== skin.id) {
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@iasiv5/dsh-skins",
3
3
  "description": "DSH Web 皮肤插件:多套品牌皮肤侧栏热切换。",
4
4
  "icon": "./icon.svg",
5
- "version": "1.5.19",
5
+ "version": "1.5.20",
6
6
  "homepage": "https://github.com/iasiv5/dsh-skins#readme",
7
7
  "repository": {
8
8
  "type": "git",
@@ -82,7 +82,7 @@
82
82
  "build": "node scripts/build-client.mjs",
83
83
  "watch": "node scripts/build-client.mjs --watch",
84
84
  "check:lockfile": "pnpm install --frozen-lockfile",
85
- "test": "pnpm run build && node smoke-test.cjs && node --test tests/*.test.mjs",
85
+ "test": "pnpm run build && node smoke-test.cjs && node scripts/repro-stale-grid-paint.mjs --gate && node scripts/repro-fullscreen-collapse.mjs --gate && node --test tests/*.test.mjs",
86
86
  "verify:readme": "node scripts/verify-readme-pairing.mjs",
87
87
  "verify:bundle": "node scripts/verify-bundle-guard.mjs",
88
88
  "verify:hooks": "node scripts/verify-upstream-hooks.mjs",