@lark-apaas/coding-registry-buildless 0.1.21 → 0.1.23

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.
@@ -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
@@ -224,7 +225,15 @@
224
225
  * reuses. It costs one thing — render() also resets that img's `sizes`,
225
226
  * discarding the rail-width hint _materialize sets; the URL still matches the
226
227
  * slide's, so the thumb reuses the slide's decoded bitmap. */
227
- const KEEP_LIVE_PREFIXES = ['cd-'];
228
+ /* The device frames (ios-/android-/macos-/browser-) get in on the *other* half
229
+ * of the rule. Their content lives in a shadow root, so cloneNode captures none
230
+ * of it — for them re-mounting is not "adds nothing", it is the only way to get
231
+ * the content at all. They do satisfy clause 2: purely presentational, read
232
+ * attributes and paint the shadow, no storage and no messages.
233
+ * design-canvas is deliberately NOT on this list — it reads localStorage,
234
+ * fetches its sidecar and posts to the host on mount, so running it once per
235
+ * thumbnail would corrupt the real state. */
236
+ const KEEP_LIVE_PREFIXES = ['cd-', 'ios-', 'android-', 'macos-', 'browser-'];
228
237
  const keepsCloneLive = (el) => KEEP_LIVE_PREFIXES.some((p) => el.localName.startsWith(p));
229
238
 
230
239
  /* Why a clone needs a substrate rule of its own: _collectAuthorCss samples
@@ -607,11 +616,13 @@
607
616
  Rotate only while the device is physically portrait. Once the user
608
617
  turns the phone (system auto-rotate) the viewport itself is landscape,
609
618
  and stacking another 90° would render slides sideways — so landscape
610
- falls back to a plain full-bleed fit. _fitMobilePresent and
611
- _mobPresentZone branch on the same orientation test. */
619
+ falls back to a plain full-bleed fit. data-present-portrait is the
620
+ host asking for an upright presentation, which suppresses the rotation
621
+ on a portrait device too. This selector is the third copy of the
622
+ predicate in _presentRotated — keep all three in phase. */
612
623
  :host([data-mobile-present]) .stage { background: #000; }
613
624
  @media (orientation: portrait) {
614
- :host([data-mobile-present]) .stage {
625
+ :host([data-mobile-present]:not([data-present-portrait])) .stage {
615
626
  inset: auto;
616
627
  left: 50%;
617
628
  top: 50%;
@@ -882,6 +893,10 @@
882
893
  this._snthumb = /[?&](_snthumb|thumbnail)=/.test(location.search);
883
894
  this._preflight = /[?&]preflight=/.test(location.search);
884
895
  this._mobile = isMobileUA();
896
+ // Orientation the host asked presenting to enter in. Sticky: only a
897
+ // message that actually carries `orientation` moves it, so replays and
898
+ // any other bare {on} message leave the current orientation alone.
899
+ this._presentPortrait = false;
885
900
  // Layout-driven size signal. window.resize is unreliable inside
886
901
  // prerendered/reframed iOS WebViews and host-managed iframes — the
887
902
  // frame can change size without any resize event, leaving a layout
@@ -1896,7 +1911,17 @@
1896
1911
  _syncMobileMode() {
1897
1912
  const eligible = (this._mobile || this._preflight) && !this.hasAttribute('noscale') && !this._snthumb;
1898
1913
  this.toggleAttribute('data-mobile-list', eligible && !this._presenting);
1899
- this.toggleAttribute('data-mobile-present', eligible && !!this._presenting && !this._preflight);
1914
+ const present = eligible && !!this._presenting && !this._preflight;
1915
+ this.toggleAttribute('data-mobile-present', present);
1916
+ this.toggleAttribute('data-present-portrait', present && this._presentPortrait);
1917
+ }
1918
+
1919
+ // Single source of truth for "is the presenting stage CSS-rotated?".
1920
+ // Read by the fit and the tap zones; the @media (orientation: portrait)
1921
+ // rule above is the same predicate spelled in CSS, reached through the
1922
+ // data-present-portrait attribute _syncMobileMode derives from this state.
1923
+ _presentRotated() {
1924
+ return !this._presentPortrait && window.innerHeight >= window.innerWidth;
1900
1925
  }
1901
1926
 
1902
1927
  _fitMobileList() {
@@ -2037,12 +2062,12 @@
2037
2062
  this.removeAttribute('data-windowed');
2038
2063
  this._canvas.style.width = this.designWidth + 'px';
2039
2064
  this._canvas.style.height = this.designHeight + 'px';
2040
- // Portrait: the stage is CSS-rotated, so its logical size is the
2041
- // swapped viewport. Landscape (user physically rotated): no rotation,
2042
- // plain fit. The CSS branches on the same orientation query.
2043
- const portrait = window.innerHeight >= window.innerWidth;
2044
- const lw = portrait ? window.innerHeight : window.innerWidth;
2045
- const lh = portrait ? window.innerWidth : window.innerHeight;
2065
+ // Rotated: the stage is CSS-rotated, so its logical size is the swapped
2066
+ // viewport. Otherwise (device already landscape, or the host asked for
2067
+ // an upright presentation) it is a plain fit into the viewport.
2068
+ const rotated = this._presentRotated();
2069
+ const lw = rotated ? window.innerHeight : window.innerWidth;
2070
+ const lh = rotated ? window.innerWidth : window.innerHeight;
2046
2071
  const s = Math.min(lw / this.designWidth, lh / this.designHeight);
2047
2072
  this._canvas.style.transform = 'scale(' + s + ')';
2048
2073
  }
@@ -2121,13 +2146,25 @@
2121
2146
  _onMessage(e) {
2122
2147
  const d = e.data;
2123
2148
  if (d && d.type === 'miaoda:deck:presenting' && typeof d.on === 'boolean') {
2149
+ // orientation is optional and sticky — the host owns it end to end, and
2150
+ // a message that omits it (a mirror replay, or a host that predates the
2151
+ // field) means "unchanged", never "back to landscape". Absent field on
2152
+ // a fresh deck therefore keeps the historical rotate-on-portrait
2153
+ // behaviour, which is what the default has to be.
2154
+ const portrait = d.orientation === 'portrait';
2155
+ const flipped = d.orientation != null && portrait !== this._presentPortrait;
2156
+ if (d.orientation != null) this._presentPortrait = portrait;
2124
2157
  // Same-value messages (incl. mirror replays routed back through here)
2125
2158
  // must not re-run the layout work or re-emit. _presenting starts
2126
- // undefined, so the first message always lands.
2127
- if (d.on === this._presenting) return;
2159
+ // undefined, so the first message always lands. The one same-value
2160
+ // case that still has to land is an orientation flip while presenting:
2161
+ // it carries the same `on` but needs the relayout.
2162
+ if (d.on === this._presenting && !(flipped && d.on)) return;
2128
2163
  this._presenting = d.on;
2129
2164
  this._syncMobileMode();
2130
- if (!d.on) this._toggleMobUi(false);
2165
+ // Leaving presenting, or flipping to upright (which has no control
2166
+ // layer at all), both have to take an open one down with them.
2167
+ if (!d.on || this._presentPortrait) this._toggleMobUi(false);
2131
2168
  this._syncRailHidden();
2132
2169
  this._syncNotesHidden();
2133
2170
  this._closeMenu();
@@ -2221,8 +2258,12 @@
2221
2258
  e.preventDefault();
2222
2259
  if (this.hasAttribute('data-mobile-present')) {
2223
2260
  const zone = this._mobPresentZone(e);
2224
- if (zone === 'menu') this._toggleMobUi();
2225
- else {
2261
+ if (zone === 'menu') {
2262
+ // Upright presenting ships no control layer: the host owns the exit
2263
+ // chrome and the page navigation there, so the middle third stays
2264
+ // inert rather than offering a second, conflicting way in.
2265
+ if (!this._presentPortrait) this._toggleMobUi();
2266
+ } else {
2226
2267
  this._advance(zone === 'prev' ? -1 : 1, 'tap');
2227
2268
  if (this.hasAttribute('data-mob-ui')) this._toggleMobUi(true);
2228
2269
  }
@@ -2238,10 +2279,9 @@
2238
2279
  // these comparisons — single point of change).
2239
2280
  _mobPresentZone(e) {
2240
2281
  const rect = this._stage.getBoundingClientRect();
2241
- // Portrait = stage is rotated, visual x comes from viewport y;
2242
- // landscape = no rotation, viewport x directly. Same branch as the fit.
2243
- const portrait = window.innerHeight >= window.innerWidth;
2244
- const x = portrait
2282
+ // Rotated stage = the visual x axis comes from viewport y; unrotated =
2283
+ // viewport x directly. Same predicate as the fit.
2284
+ const x = this._presentRotated()
2245
2285
  ? (e.clientY - rect.top) / Math.max(1, rect.height)
2246
2286
  : (e.clientX - rect.left) / Math.max(1, rect.width);
2247
2287
  return x < 1 / 3 ? 'prev' : x > 2 / 3 ? 'next' : 'menu';