@ds-gn/ui 0.11.3 → 0.12.0

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.
@@ -2200,9 +2200,17 @@
2200
2200
  },
2201
2201
  {
2202
2202
  "kind": "field",
2203
- "name": "_inView",
2203
+ "name": "_observedRotationThreshold",
2204
+ "type": {
2205
+ "text": "number"
2206
+ },
2204
2207
  "privacy": "private",
2205
- "default": "new IntersectionController<boolean>(this, { config: { threshold: 0 }, callback: (entries) => entries[entries.length - 1]?.isIntersecting ?? false, })"
2208
+ "default": "0"
2209
+ },
2210
+ {
2211
+ "kind": "field",
2212
+ "name": "_view",
2213
+ "privacy": "private"
2206
2214
  },
2207
2215
  {
2208
2216
  "kind": "field",
@@ -2211,7 +2219,7 @@
2211
2219
  "text": "AutoRotateController"
2212
2220
  },
2213
2221
  "privacy": "private",
2214
- "default": "new AutoRotateController( this, DEFAULT_DURATION, () => this._inView.value === true, )"
2222
+ "default": "new AutoRotateController(this, DEFAULT_DURATION, () => this._rotationVisible(), )"
2215
2223
  },
2216
2224
  {
2217
2225
  "kind": "field",
@@ -2263,6 +2271,16 @@
2263
2271
  "default": "DEFAULT_DURATION",
2264
2272
  "attribute": "duration"
2265
2273
  },
2274
+ {
2275
+ "kind": "field",
2276
+ "name": "rotationThreshold",
2277
+ "type": {
2278
+ "text": "number"
2279
+ },
2280
+ "default": "0",
2281
+ "description": "Visible fraction required for auto-rotation, from 0 to 1.",
2282
+ "attribute": "rotation-threshold"
2283
+ },
2266
2284
  {
2267
2285
  "kind": "field",
2268
2286
  "name": "codePath",
@@ -2333,6 +2351,37 @@
2333
2351
  "attribute": "motion",
2334
2352
  "reflects": true
2335
2353
  },
2354
+ {
2355
+ "kind": "field",
2356
+ "name": "_rotationThreshold",
2357
+ "privacy": "private",
2358
+ "description": "`rotationThreshold` clamped to 0–1; NaN falls back to 0.",
2359
+ "readonly": true
2360
+ },
2361
+ {
2362
+ "kind": "method",
2363
+ "name": "_rotationVisible",
2364
+ "privacy": "private"
2365
+ },
2366
+ {
2367
+ "kind": "method",
2368
+ "name": "_observeVisibility",
2369
+ "privacy": "private",
2370
+ "parameters": [
2371
+ {
2372
+ "name": "rotationThreshold",
2373
+ "type": {
2374
+ "text": "number"
2375
+ }
2376
+ }
2377
+ ]
2378
+ },
2379
+ {
2380
+ "kind": "method",
2381
+ "name": "_syncVisibilityObserver",
2382
+ "privacy": "private",
2383
+ "description": "Observer thresholds are fixed at construction, so a new rotation\nthreshold swaps the observer. The last report carries over, so a running\ntimer keeps running until the new observer reports."
2384
+ },
2336
2385
  {
2337
2386
  "kind": "method",
2338
2387
  "name": "_updateMotion",
@@ -2552,6 +2601,15 @@
2552
2601
  },
2553
2602
  "default": "DEFAULT_DURATION"
2554
2603
  },
2604
+ {
2605
+ "name": "rotation-threshold",
2606
+ "fieldName": "rotationThreshold",
2607
+ "type": {
2608
+ "text": "number"
2609
+ },
2610
+ "default": "0",
2611
+ "description": "Visible fraction required for auto-rotation, from 0 to 1."
2612
+ },
2555
2613
  {
2556
2614
  "name": "code-path",
2557
2615
  "fieldName": "codePath",
@@ -18,7 +18,8 @@ export declare class FeatureTabs extends Base {
18
18
  paddingTop?: SectionSpaceType;
19
19
  /** Space below the section. Unset follows the inherited section space. */
20
20
  paddingBottom?: SectionSpaceType;
21
- private _inView;
21
+ private _observedRotationThreshold;
22
+ private _view;
22
23
  private _rotate;
23
24
  private _motion;
24
25
  /** The feature whose detail last faded in; the fade replays when it changes. */
@@ -28,6 +29,8 @@ export declare class FeatureTabs extends Base {
28
29
  surface: SurfaceType;
29
30
  features: FeatureTabData[];
30
31
  duration: number;
32
+ /** Visible fraction required for auto-rotation, from 0 to 1. */
33
+ rotationThreshold: number;
31
34
  /** Working-directory path shown in each tab's code block. */
32
35
  codePath: string;
33
36
  /** Leading text of the auto-rotate status (the `· Ns` is derived from duration). */
@@ -48,7 +51,18 @@ export declare class FeatureTabs extends Base {
48
51
  */
49
52
  motion: MotionType;
50
53
  constructor();
54
+ /** `rotationThreshold` clamped to 0–1; NaN falls back to 0. */
55
+ private get _rotationThreshold();
56
+ private _rotationVisible;
57
+ private _observeVisibility;
58
+ /**
59
+ * Observer thresholds are fixed at construction, so a new rotation
60
+ * threshold swaps the observer. The last report carries over, so a running
61
+ * timer keeps running until the new observer reports.
62
+ */
63
+ private _syncVisibilityObserver;
51
64
  firstUpdated(): void;
65
+ willUpdate(changedProperties: Map<string | number | symbol, unknown>): void;
52
66
  /**
53
67
  * Decide the motion from the first visibility report: a section already
54
68
  * on screen stays static; one off screen holds its parts hidden (from
@@ -292,6 +292,7 @@ class IntersectionController {
292
292
  // components/FeatureTabs/src/FeatureTabs.ts
293
293
  import {
294
294
  html as html2,
295
+ isServer as isServer2,
295
296
  nothing,
296
297
  svg
297
298
  } from "lit";
@@ -345,9 +346,15 @@ var styles2 = css2`/*! tailwindcss v4.3.0 | MIT License | https://tailwindcss.co
345
346
  }
346
347
  }
347
348
  @layer utilities {
349
+ .visible {
350
+ visibility: visible;
351
+ }
348
352
  .absolute {
349
353
  position: absolute;
350
354
  }
355
+ .fixed {
356
+ position: fixed;
357
+ }
351
358
  .relative {
352
359
  position: relative;
353
360
  }
@@ -1022,6 +1029,29 @@ var HEADING_STAGGER = 20;
1022
1029
  var HEADING_MAX_SPREAD = 500;
1023
1030
  var HEADING_RISE = 4;
1024
1031
  var MOTION_OVERLAP = 160;
1032
+ var VISIBILITY_TOLERANCE = 0.001;
1033
+ var VISIBILITY_STEP = 0.01;
1034
+ var VISIBILITY_STEPS = Array.from({ length: 1 / VISIBILITY_STEP + 1 }, (_, i) => i * VISIBILITY_STEP);
1035
+ var readVisibility = (entry) => {
1036
+ if (!entry?.isIntersecting) {
1037
+ return { intersecting: false, fraction: 0, slack: 0 };
1038
+ }
1039
+ const section = entry.boundingClientRect.height;
1040
+ const viewport = entry.rootBounds?.height ?? 0;
1041
+ if (viewport === 0 || section <= viewport) {
1042
+ return {
1043
+ intersecting: true,
1044
+ fraction: entry.intersectionRatio,
1045
+ slack: VISIBILITY_TOLERANCE
1046
+ };
1047
+ }
1048
+ const scale = section / viewport;
1049
+ return {
1050
+ intersecting: true,
1051
+ fraction: Math.min(1, entry.intersectionRatio * scale),
1052
+ slack: VISIBILITY_STEP * scale + VISIBILITY_TOLERANCE
1053
+ };
1054
+ };
1025
1055
  var FEATURE_TAB_ICONS = {
1026
1056
  terminal: svg`<polyline points="4 17 10 11 4 5"/><line x1="12" y1="19" x2="20" y2="19"/>`,
1027
1057
  brain: svg`<path d="M9 4.5a2.5 2.5 0 0 0-2.5 2.5v.5a2.5 2.5 0 0 0-2 4 2.5 2.5 0 0 0 2 4v.5A2.5 2.5 0 0 0 9 18.5a2.5 2.5 0 0 0 3-2.5V6a2.5 2.5 0 0 0-3-1.5z"/><path d="M15 4.5A2.5 2.5 0 0 1 17.5 7v.5a2.5 2.5 0 0 1 2 4 2.5 2.5 0 0 1-2 4v.5A2.5 2.5 0 0 1 15 18.5a2.5 2.5 0 0 1-3-2.5V6a2.5 2.5 0 0 1 3-1.5z"/>`,
@@ -1052,14 +1082,13 @@ class FeatureTabs extends Base {
1052
1082
  static styles = [Base.styles, FeatureTabs_default];
1053
1083
  constructor() {
1054
1084
  super();
1055
- this._inView = new IntersectionController(this, {
1056
- config: { threshold: 0 },
1057
- callback: (entries) => entries[entries.length - 1]?.isIntersecting ?? false
1058
- });
1085
+ this._observedRotationThreshold = 0;
1086
+ this._view = this._observeVisibility(0);
1059
1087
  this._motion = new MotionController(this);
1060
1088
  this.surface = surfaceValues.DARK;
1061
1089
  this.features = DEFAULT_FEATURES;
1062
1090
  this.duration = DEFAULT_DURATION;
1091
+ this.rotationThreshold = 0;
1063
1092
  this.codePath = DEFAULT_CODE_PATH;
1064
1093
  this.rotatingLabel = DEFAULT_ROTATING_LABEL;
1065
1094
  this.pausedLabel = DEFAULT_PAUSED_LABEL;
@@ -1067,13 +1096,48 @@ class FeatureTabs extends Base {
1067
1096
  this.resumeAriaLabel = DEFAULT_RESUME_ARIA_LABEL;
1068
1097
  this.tabsAriaLabel = DEFAULT_TABS_ARIA_LABEL;
1069
1098
  this.motion = motionValues.OFF;
1070
- this._rotate = new AutoRotateController(this, DEFAULT_DURATION, () => this._inView.value === true);
1099
+ this._rotate = new AutoRotateController(this, DEFAULT_DURATION, () => this._rotationVisible());
1100
+ }
1101
+ get _rotationThreshold() {
1102
+ return Number.isNaN(this.rotationThreshold) ? 0 : Math.min(1, Math.max(0, this.rotationThreshold));
1103
+ }
1104
+ _rotationVisible() {
1105
+ const view = this._view.value;
1106
+ return view?.intersecting === true && view.fraction >= this._rotationThreshold - view.slack;
1107
+ }
1108
+ _observeVisibility(rotationThreshold) {
1109
+ const threshold = rotationThreshold > 0 ? [
1110
+ ...VISIBILITY_STEPS,
1111
+ Math.max(0, rotationThreshold - VISIBILITY_TOLERANCE)
1112
+ ] : 0;
1113
+ return new IntersectionController(this, {
1114
+ config: { threshold },
1115
+ callback: (entries) => readVisibility(entries[entries.length - 1])
1116
+ });
1117
+ }
1118
+ _syncVisibilityObserver() {
1119
+ const threshold = this._rotationThreshold;
1120
+ if (threshold === this._observedRotationThreshold)
1121
+ return;
1122
+ this._observedRotationThreshold = threshold;
1123
+ const previous = this._view;
1124
+ this._view = this._observeVisibility(threshold);
1125
+ this._view.value = previous.value;
1126
+ this.removeController(previous);
1127
+ if (!isServer2)
1128
+ previous.hostDisconnected();
1071
1129
  }
1072
1130
  firstUpdated() {
1073
1131
  this._rotate.resume(this.features.length);
1074
1132
  }
1133
+ willUpdate(changedProperties) {
1134
+ super.willUpdate(changedProperties);
1135
+ if (changedProperties.has("rotationThreshold")) {
1136
+ this._syncVisibilityObserver();
1137
+ }
1138
+ }
1075
1139
  _updateMotion() {
1076
- const inView = this._inView.value;
1140
+ const inView = this._view.value?.intersecting;
1077
1141
  if (inView === undefined)
1078
1142
  return;
1079
1143
  if (this._motionState === undefined) {
@@ -1370,6 +1434,9 @@ __legacyDecorateClassTS([
1370
1434
  __legacyDecorateClassTS([
1371
1435
  property({ type: Number })
1372
1436
  ], FeatureTabs.prototype, "duration", undefined);
1437
+ __legacyDecorateClassTS([
1438
+ property({ type: Number, attribute: "rotation-threshold" })
1439
+ ], FeatureTabs.prototype, "rotationThreshold", undefined);
1373
1440
  __legacyDecorateClassTS([
1374
1441
  property({ type: String, attribute: "code-path" })
1375
1442
  ], FeatureTabs.prototype, "codePath", undefined);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ds-gn/ui",
3
- "version": "0.11.3",
3
+ "version": "0.12.0",
4
4
  "description": "UI components for the DS-GN Design System",
5
5
  "type": "module",
6
6
  "exports": {