@lark-apaas/coding-registry-buildless 0.1.5 → 0.1.6

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lark-apaas/coding-registry-buildless",
3
- "version": "0.1.5",
3
+ "version": "0.1.6",
4
4
  "description": "Miaoda registry (buildless runtime) — shadcn-style copy-in SDKs (globals + CDN-pinned, browser Babel) for buildless static HTML stacks",
5
5
  "type": "module",
6
6
  "files": [
@@ -38,6 +38,10 @@
38
38
  * a scrollable vertical card list (browse) and a rotated fullscreen
39
39
  * presenting mode. This needs NO authoring changes: never hand-roll
40
40
  * mobile/responsive slide layouts. Desktop behaviour is unchanged.
41
+ * Browse mode renders authored CSS animations/transitions at their
42
+ * end state (presenting keeps them) — design animations so the end
43
+ * state is the complete slide; never gate content visibility on an
44
+ * animation having played.
41
45
  *
42
46
  * Slides are HIDDEN, not unmounted. Non-active slides stay in the DOM with
43
47
  * `visibility: hidden` + `opacity: 0`, so their state (videos, iframes,
@@ -698,7 +702,18 @@
698
702
  // slide 1, so ?thumbnail=1 alone yields a clean first-slide frame.
699
703
  this._snthumb = /[?&](_snthumb|thumbnail)=/.test(location.search);
700
704
  this._mobile = isMobileUA();
705
+ // Layout-driven size signal. window.resize is unreliable inside
706
+ // prerendered/reframed iOS WebViews and host-managed iframes — the
707
+ // frame can change size without any resize event, leaving a layout
708
+ // computed at a bogus early width in place forever. ResizeObserver
709
+ // fires from layout itself, so it catches every real size change.
710
+ // Mobile-only: desktop windows deliver resize dependably.
711
+ if (this._mobile && typeof ResizeObserver !== 'undefined') {
712
+ this._mobSizeRo = new ResizeObserver(() => this._onResize());
713
+ this._mobSizeRo.observe(document.documentElement);
714
+ }
701
715
  this._syncMobileMode();
716
+ if (this._mobile) this._syncMobFreezeRule();
702
717
  if (this._snthumb) this.setAttribute('no-rail', '');
703
718
  this._render();
704
719
  this._loadNotes();
@@ -875,6 +890,20 @@
875
890
  * pendingOut (when provided) and leave a placeholder until the fetch lands. */
876
891
  _collectAuthorCss(pendingOut) {
877
892
  return Array.from(document.styleSheets).map((sh) => {
893
+ // Inline <style>: quote the author's source text instead of
894
+ // re-serializing the CSSOM. Chromium serializes a var()-carrying
895
+ // `background` shorthand into EMPTY longhands whenever the same
896
+ // rule also sets any background-* longhand — exactly the
897
+ // gradient-text idiom (background: linear-gradient(var(--accent)…);
898
+ // background-clip: text), whose headline then renders invisible
899
+ // (text-fill stays transparent, gradient gone). Empty textContent
900
+ // falls through to the CSSOM path: CSS-in-JS "speedy" injectors
901
+ // (emotion, styled-components) insertRule() into a blank <style>,
902
+ // so raw text would snapshot them as nothing.
903
+ const node = sh.ownerNode;
904
+ if (node && node.tagName === 'STYLE' && node.textContent.trim()) {
905
+ return node.textContent;
906
+ }
878
907
  try {
879
908
  return Array.from(sh.cssRules).map((r) => r.cssText).join('\n');
880
909
  } catch (e) {
@@ -906,19 +935,29 @@
906
935
  const authorCss = rawCss
907
936
  // The shadow host is featureless outside the functional :host(...)
908
937
  // form, so any compound on :root — [attr], .class, #id, :pseudo —
909
- // must become :host(<compound>) not :host<compound>. Same for the
910
- // html type selector (Tailwind class-strategy dark mode emits
911
- // html.dark; Pico uses html[data-theme]), which has nothing to
912
- // match inside the thumb's shadow tree.
938
+ // must become :host(<compound>) not :host<compound>. :root and
939
+ // :host are both pseudo-classes, so this pair is also
940
+ // specificity-neutral as written.
913
941
  .replace(/:root((?:\[[^\]]*\]|[.#][-\w]+|:[-\w]+(?:\([^)]*\))?)+)/g, ':host($1)')
914
942
  .replace(/:root\b/g, ':host')
915
- .replace(/(^|[\s,>~+(}])html((?:\[[^\]]*\]|[.#][-\w]+|:[-\w]+(?:\([^)]*\))?)+)(?![-\w])/g, '$1:host($2)')
916
- .replace(/(^|[\s,>~+(}])html(?![-\w])/g, '$1:host')
917
- // Same rewrite for the deck-stage type selector (see the doc comment).
943
+ // html / deck-stage are TYPE selectors (0,0,1) while :host is a
944
+ // pseudo-class (0,1,0): aliasing to bare :host would silently
945
+ // promote every such rule a whole specificity level and flip the
946
+ // author's cascade — `deck-stage section { background }` written
947
+ // as a low-priority default would beat the per-slide `.slide-xxx`
948
+ // class that overrides it, washing thumbnails out while the stage
949
+ // renders correctly. :where(:host) zeroes the alias and
950
+ // :host(:where(…)) pins compounds at plain pseudo-class level, so
951
+ // a rewritten rule stays within one type-unit of its document
952
+ // specificity and can never jump past a class-level override.
953
+ // (html compounds appear in the wild: Tailwind class-strategy dark
954
+ // mode emits html.dark; Pico uses html[data-theme].)
955
+ .replace(/(^|[\s,>~+(}])html((?:\[[^\]]*\]|[.#][-\w]+|:[-\w]+(?:\([^)]*\))?)+)(?![-\w])/g, '$1:host(:where($2))')
956
+ .replace(/(^|[\s,>~+(}])html(?![-\w])/g, '$1:where(:host)')
918
957
  // The negative-lookahead (?![-\w]) keeps `.deck-stage-title` and the
919
958
  // `--deck-stage-*` custom-property names untouched.
920
- .replace(/(^|[\s,>~+(}])deck-stage((?:\[[^\]]*\]|[.#][-\w]+|:[-\w]+(?:\([^)]*\))?)+)(?![-\w])/g, '$1:host($2)')
921
- .replace(/(^|[\s,>~+(}])deck-stage(?![-\w])/g, '$1:host');
959
+ .replace(/(^|[\s,>~+(}])deck-stage((?:\[[^\]]*\]|[.#][-\w]+|:[-\w]+(?:\([^)]*\))?)+)(?![-\w])/g, '$1:host(:where($2))')
960
+ .replace(/(^|[\s,>~+(}])deck-stage(?![-\w])/g, '$1:where(:host)');
922
961
  // Every custom property the author references. _syncThumbHostAttrs
923
962
  // mirrors each one's *computed* value at <deck-stage> onto the
924
963
  // thumb host so the live value wins over the :host default above
@@ -977,6 +1016,7 @@
977
1016
  }
978
1017
 
979
1018
  disconnectedCallback() {
1019
+ if (this._mobSizeRo) { this._mobSizeRo.disconnect(); this._mobSizeRo = null; }
980
1020
  window.removeEventListener('keydown', this._onKey);
981
1021
  window.removeEventListener('resize', this._onResize);
982
1022
  window.removeEventListener('message', this._onMessage);
@@ -1324,6 +1364,29 @@
1324
1364
  'animation-play-state: running !important; transition-duration: 0s !important; } }';
1325
1365
  }
1326
1366
 
1367
+ /** Mobile browse (data-mobile-list) force-shows every card at once, so
1368
+ * authored CSS animations all fire together on load — and the ones gated
1369
+ * on [data-deck-active] re-fire as scrolling moves that flag between
1370
+ * cards. Freeze them at their end state while browsing. Same jump-to-end
1371
+ * recipe as the print rule above (NOT animation:none, so a deck that
1372
+ * keeps a hidden base state still renders its content), keyed on the
1373
+ * host attribute so presenting/desktop keep their animations and no JS
1374
+ * toggling is needed. Document-level like the print rule: author
1375
+ * animations style light-DOM slide content that shadow CSS can't reach,
1376
+ * and <deck-stage> itself is a light-DOM element document CSS can key on. */
1377
+ _syncMobFreezeRule() {
1378
+ const id = 'deck-stage-mob-freeze';
1379
+ if (document.getElementById(id)) return;
1380
+ const tag = document.createElement('style');
1381
+ tag.id = id;
1382
+ tag.textContent =
1383
+ 'deck-stage[data-mobile-list] *, deck-stage[data-mobile-list] *::before, deck-stage[data-mobile-list] *::after {' +
1384
+ ' animation-delay: -99s !important; animation-duration: .001s !important;' +
1385
+ ' animation-iteration-count: 1 !important; animation-fill-mode: both !important;' +
1386
+ ' animation-play-state: running !important; transition-duration: 0s !important; }';
1387
+ document.head.appendChild(tag);
1388
+ }
1389
+
1327
1390
  _onSlotChange() {
1328
1391
  // Rail mutations (delete/move) already reconcile synchronously and
1329
1392
  // emit slidechange with reason 'api'; skip the async slotchange that
@@ -1478,6 +1541,12 @@
1478
1541
  _fitMobileList() {
1479
1542
  const dw = this.designWidth, dh = this.designHeight;
1480
1543
  const vw = window.innerWidth;
1544
+ // Hosts (in-app WebViews, portal iframes) can run us before our frame
1545
+ // has a real size — committing a layout at a bogus width shrinks every
1546
+ // card to near-zero, and the correcting resize event is NOT guaranteed
1547
+ // to follow on iOS. Bail and let the documentElement ResizeObserver
1548
+ // (registered in connectedCallback) re-run us when layout gets a size.
1549
+ if (!vw || vw < 40) { this._mobLastVw = -1; return; }
1481
1550
  this._mobLastVw = vw; // baseline for _onResize's height-only skip
1482
1551
  const stage = this._canvas.parentElement;
1483
1552
  // Clear the stage inset the desktop _fit() wrote inline.