@lark-apaas/coding-registry-buildless 0.1.22 → 0.1.24-alpha.20260902135158

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.22",
3
+ "version": "0.1.24-alpha.20260902135158",
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": [
@@ -35,9 +35,10 @@
35
35
  * a `deckchange`
36
36
  * CustomEvent on the element: detail = {action, from, to, slide}.
37
37
  * (g) mobile — on mobile devices the same deck automatically renders as
38
- * a scrollable vertical card list (browse) and a rotated fullscreen
39
- * presenting mode. This needs NO authoring changes: never hand-roll
40
- * mobile/responsive slide layouts. Desktop behaviour is unchanged.
38
+ * a scrollable vertical card list (browse) and a fullscreen presenting
39
+ * mode, pseudo-landscape or upright as the host asks. This needs NO
40
+ * authoring changes: never hand-roll mobile/responsive slide layouts.
41
+ * Desktop behaviour is unchanged.
41
42
  * Browse mode renders each slide's static base state and never fires
42
43
  * entrance animations — exactly like the thumbnail rail and print
43
44
  * (presenting keeps animations). One more surface that requires the
@@ -100,16 +101,22 @@
100
101
  * content genuinely needs interactive behaviour static HTML can't express.
101
102
  * - Do NOT set position/inset/width/height on the slide <section> elements —
102
103
  * the component absolutely positions every slotted child for you.
103
- * - Entrance animations: make the visible end-state the base style and
104
- * animate *from* hidden. The thumbnail rail, print, and reduced-motion
105
- * render each slide's static base state and never fire the animation, so
106
- * a hidden base (opacity:0 on the rule itself) renders blank there while
107
- * the live slide looks fine. Keep the hidden state only in the keyframes
108
- * `from`, and gate the animation on [data-deck-active] and the motion
109
- * query, e.g.:
104
+ * - Entrance animations: the base style IS the finished slide — keyframes
105
+ * only describe the path into it. Litmus test: delete every `animation`
106
+ * declaration and the resting picture must not change by a pixel. The
107
+ * thumbnail rail, print, and reduced-motion render exactly that
108
+ * animation-less base state, so any divergence makes them disagree with
109
+ * the live slide. The two frequent violations, one per direction: a
110
+ * hidden base (opacity:0 on the rule itself) blanks those surfaces; and
111
+ * a keyframed transform REPLACES the base transform wholesale — an
112
+ * element centered via `transform: translateY(-50%)` given a scale()
113
+ * entrance loses its centering on the active slide only. Keep start
114
+ * states in `from` only and omit `to`: omitted properties converge on
115
+ * the base value by construction. Gate on [data-deck-active] and the
116
+ * motion query, e.g.:
110
117
  * .fade-up { opacity: 1 }
111
118
  * @media (prefers-reduced-motion:no-preference){ [data-deck-active] .fade-up{ animation: fadeUp .5s both } }
112
- * @keyframes fadeUp { from{ opacity:0; transform:translateY(24px) } to{ opacity:1; transform:none } }
119
+ * @keyframes fadeUp { from{ opacity:0; translate:0 24px } }
113
120
  * Avoid infinite decorative loops on slide content.
114
121
  *
115
122
  * window.__miaodaMirror (defined below) is Magic-Share sync plumbing between
@@ -615,11 +622,13 @@
615
622
  Rotate only while the device is physically portrait. Once the user
616
623
  turns the phone (system auto-rotate) the viewport itself is landscape,
617
624
  and stacking another 90° would render slides sideways — so landscape
618
- falls back to a plain full-bleed fit. _fitMobilePresent and
619
- _mobPresentZone branch on the same orientation test. */
625
+ falls back to a plain full-bleed fit. data-present-portrait is the
626
+ host asking for an upright presentation, which suppresses the rotation
627
+ on a portrait device too. This selector is the third copy of the
628
+ predicate in _presentRotated — keep all three in phase. */
620
629
  :host([data-mobile-present]) .stage { background: #000; }
621
630
  @media (orientation: portrait) {
622
- :host([data-mobile-present]) .stage {
631
+ :host([data-mobile-present]:not([data-present-portrait])) .stage {
623
632
  inset: auto;
624
633
  left: 50%;
625
634
  top: 50%;
@@ -890,6 +899,10 @@
890
899
  this._snthumb = /[?&](_snthumb|thumbnail)=/.test(location.search);
891
900
  this._preflight = /[?&]preflight=/.test(location.search);
892
901
  this._mobile = isMobileUA();
902
+ // Orientation the host asked presenting to enter in. Sticky: only a
903
+ // message that actually carries `orientation` moves it, so replays and
904
+ // any other bare {on} message leave the current orientation alone.
905
+ this._presentPortrait = false;
893
906
  // Layout-driven size signal. window.resize is unreliable inside
894
907
  // prerendered/reframed iOS WebViews and host-managed iframes — the
895
908
  // frame can change size without any resize event, leaving a layout
@@ -1904,7 +1917,17 @@
1904
1917
  _syncMobileMode() {
1905
1918
  const eligible = (this._mobile || this._preflight) && !this.hasAttribute('noscale') && !this._snthumb;
1906
1919
  this.toggleAttribute('data-mobile-list', eligible && !this._presenting);
1907
- this.toggleAttribute('data-mobile-present', eligible && !!this._presenting && !this._preflight);
1920
+ const present = eligible && !!this._presenting && !this._preflight;
1921
+ this.toggleAttribute('data-mobile-present', present);
1922
+ this.toggleAttribute('data-present-portrait', present && this._presentPortrait);
1923
+ }
1924
+
1925
+ // Single source of truth for "is the presenting stage CSS-rotated?".
1926
+ // Read by the fit and the tap zones; the @media (orientation: portrait)
1927
+ // rule above is the same predicate spelled in CSS, reached through the
1928
+ // data-present-portrait attribute _syncMobileMode derives from this state.
1929
+ _presentRotated() {
1930
+ return !this._presentPortrait && window.innerHeight >= window.innerWidth;
1908
1931
  }
1909
1932
 
1910
1933
  _fitMobileList() {
@@ -2045,12 +2068,12 @@
2045
2068
  this.removeAttribute('data-windowed');
2046
2069
  this._canvas.style.width = this.designWidth + 'px';
2047
2070
  this._canvas.style.height = this.designHeight + 'px';
2048
- // Portrait: the stage is CSS-rotated, so its logical size is the
2049
- // swapped viewport. Landscape (user physically rotated): no rotation,
2050
- // plain fit. The CSS branches on the same orientation query.
2051
- const portrait = window.innerHeight >= window.innerWidth;
2052
- const lw = portrait ? window.innerHeight : window.innerWidth;
2053
- const lh = portrait ? window.innerWidth : window.innerHeight;
2071
+ // Rotated: the stage is CSS-rotated, so its logical size is the swapped
2072
+ // viewport. Otherwise (device already landscape, or the host asked for
2073
+ // an upright presentation) it is a plain fit into the viewport.
2074
+ const rotated = this._presentRotated();
2075
+ const lw = rotated ? window.innerHeight : window.innerWidth;
2076
+ const lh = rotated ? window.innerWidth : window.innerHeight;
2054
2077
  const s = Math.min(lw / this.designWidth, lh / this.designHeight);
2055
2078
  this._canvas.style.transform = 'scale(' + s + ')';
2056
2079
  }
@@ -2129,13 +2152,25 @@
2129
2152
  _onMessage(e) {
2130
2153
  const d = e.data;
2131
2154
  if (d && d.type === 'miaoda:deck:presenting' && typeof d.on === 'boolean') {
2155
+ // orientation is optional and sticky — the host owns it end to end, and
2156
+ // a message that omits it (a mirror replay, or a host that predates the
2157
+ // field) means "unchanged", never "back to landscape". Absent field on
2158
+ // a fresh deck therefore keeps the historical rotate-on-portrait
2159
+ // behaviour, which is what the default has to be.
2160
+ const portrait = d.orientation === 'portrait';
2161
+ const flipped = d.orientation != null && portrait !== this._presentPortrait;
2162
+ if (d.orientation != null) this._presentPortrait = portrait;
2132
2163
  // Same-value messages (incl. mirror replays routed back through here)
2133
2164
  // must not re-run the layout work or re-emit. _presenting starts
2134
- // undefined, so the first message always lands.
2135
- if (d.on === this._presenting) return;
2165
+ // undefined, so the first message always lands. The one same-value
2166
+ // case that still has to land is an orientation flip while presenting:
2167
+ // it carries the same `on` but needs the relayout.
2168
+ if (d.on === this._presenting && !(flipped && d.on)) return;
2136
2169
  this._presenting = d.on;
2137
2170
  this._syncMobileMode();
2138
- if (!d.on) this._toggleMobUi(false);
2171
+ // Leaving presenting, or flipping to upright (which has no control
2172
+ // layer at all), both have to take an open one down with them.
2173
+ if (!d.on || this._presentPortrait) this._toggleMobUi(false);
2139
2174
  this._syncRailHidden();
2140
2175
  this._syncNotesHidden();
2141
2176
  this._closeMenu();
@@ -2229,8 +2264,12 @@
2229
2264
  e.preventDefault();
2230
2265
  if (this.hasAttribute('data-mobile-present')) {
2231
2266
  const zone = this._mobPresentZone(e);
2232
- if (zone === 'menu') this._toggleMobUi();
2233
- else {
2267
+ if (zone === 'menu') {
2268
+ // Upright presenting ships no control layer: the host owns the exit
2269
+ // chrome and the page navigation there, so the middle third stays
2270
+ // inert rather than offering a second, conflicting way in.
2271
+ if (!this._presentPortrait) this._toggleMobUi();
2272
+ } else {
2234
2273
  this._advance(zone === 'prev' ? -1 : 1, 'tap');
2235
2274
  if (this.hasAttribute('data-mob-ui')) this._toggleMobUi(true);
2236
2275
  }
@@ -2246,10 +2285,9 @@
2246
2285
  // these comparisons — single point of change).
2247
2286
  _mobPresentZone(e) {
2248
2287
  const rect = this._stage.getBoundingClientRect();
2249
- // Portrait = stage is rotated, visual x comes from viewport y;
2250
- // landscape = no rotation, viewport x directly. Same branch as the fit.
2251
- const portrait = window.innerHeight >= window.innerWidth;
2252
- const x = portrait
2288
+ // Rotated stage = the visual x axis comes from viewport y; unrotated =
2289
+ // viewport x directly. Same predicate as the fit.
2290
+ const x = this._presentRotated()
2253
2291
  ? (e.clientY - rect.top) / Math.max(1, rect.height)
2254
2292
  : (e.clientX - rect.left) / Math.max(1, rect.width);
2255
2293
  return x < 1 / 3 ? 'prev' : x > 2 / 3 ? 'next' : 'menu';