@bitstack/ng-boundary 14.0.6-alpha.3 → 14.0.6-alpha.4
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/esm2020/lib/bs-boundary-context.service.mjs +1 -1
- package/esm2020/lib/bs-boundary.mjs +209 -32
- package/esm2020/lib/model.mjs +1 -1
- package/fesm2015/bitstack-ng-boundary.mjs +208 -31
- package/fesm2015/bitstack-ng-boundary.mjs.map +1 -1
- package/fesm2020/bitstack-ng-boundary.mjs +208 -31
- package/fesm2020/bitstack-ng-boundary.mjs.map +1 -1
- package/lib/bs-boundary.d.ts +99 -9
- package/lib/model.d.ts +1 -1
- package/package.json +1 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
|
+
import { BreakpointObserver } from '@angular/cdk/layout';
|
|
1
2
|
import * as i0 from '@angular/core';
|
|
2
3
|
import { Injectable, EventEmitter, inject, ElementRef, Component, Input, Output } from '@angular/core';
|
|
3
|
-
import { BreakpointObserver } from '@angular/cdk/layout';
|
|
4
4
|
import { BehaviorSubject, Subject, takeUntil, fromEvent } from 'rxjs';
|
|
5
5
|
|
|
6
6
|
class BsBoundaryContextService {
|
|
@@ -21,21 +21,112 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.3.0", ngImpor
|
|
|
21
21
|
type: Injectable
|
|
22
22
|
}] });
|
|
23
23
|
|
|
24
|
+
/**
|
|
25
|
+
* ============================================================
|
|
26
|
+
* BsBoundary Component - AWD 自適應邊界容器
|
|
27
|
+
* ============================================================
|
|
28
|
+
*
|
|
29
|
+
* 功能說明:
|
|
30
|
+
* 1. 提供響應式邊界容器,自動適應不同設備尺寸
|
|
31
|
+
* 2. 支援 5 種方向模式:auto, portrait, landscape, portraitLockRotation, landscapeLockRotation
|
|
32
|
+
* 3. 使用 CSS 變數實現動態計算,支援 px2vw() 函數
|
|
33
|
+
* 4. 相容 Chrome 74+,使用 JavaScript 計算邊界尺寸
|
|
34
|
+
*
|
|
35
|
+
* 使用範例:
|
|
36
|
+
* ```html
|
|
37
|
+
* <bs-boundary [orientation]="'auto'" [isFixedCenter]="true">
|
|
38
|
+
* <div class="content">...</div>
|
|
39
|
+
* </bs-boundary>
|
|
40
|
+
* ```
|
|
41
|
+
*
|
|
42
|
+
* CSS 變數設定:
|
|
43
|
+
* ```scss
|
|
44
|
+
* bs-boundary {
|
|
45
|
+
* --design-width: 375; // 設計稿寬度
|
|
46
|
+
* --design-height: 667; // 設計稿高度
|
|
47
|
+
* }
|
|
48
|
+
* ```
|
|
49
|
+
*/
|
|
24
50
|
class BsBoundary {
|
|
25
51
|
constructor() {
|
|
52
|
+
// ============================================================
|
|
53
|
+
// Inputs & Outputs
|
|
54
|
+
// ============================================================
|
|
55
|
+
/**
|
|
56
|
+
* 是否將內容固定在視窗中央
|
|
57
|
+
* @default false
|
|
58
|
+
*/
|
|
26
59
|
this.isFixedCenter = false;
|
|
60
|
+
/**
|
|
61
|
+
* 是否禁用指標事件
|
|
62
|
+
* @default false
|
|
63
|
+
*/
|
|
27
64
|
this.pointerEventsNone = false;
|
|
28
|
-
|
|
65
|
+
/**
|
|
66
|
+
* 方向模式
|
|
67
|
+
* - auto: 自動檢測方向
|
|
68
|
+
* - portrait: 鎖定直向
|
|
69
|
+
* - landscape: 鎖定橫向
|
|
70
|
+
* - portraitLockRotation: 強制直向(橫向設備時旋轉 -90°)
|
|
71
|
+
* - landscapeLockRotation: 強制橫向(直向設備時旋轉 90°)
|
|
72
|
+
* @default 'auto'
|
|
73
|
+
*/
|
|
74
|
+
this.orientation = 'auto';
|
|
75
|
+
/**
|
|
76
|
+
* 方向變化事件
|
|
77
|
+
* @emits true: landscape(橫屏)
|
|
78
|
+
* @emits false: portrait(直屏)
|
|
79
|
+
*/
|
|
29
80
|
this.orientationChange = new EventEmitter();
|
|
81
|
+
// ============================================================
|
|
82
|
+
// Private Dependencies
|
|
83
|
+
// ============================================================
|
|
30
84
|
this.breakpointObserver = inject(BreakpointObserver);
|
|
31
85
|
this.boundaryContext = inject(BsBoundaryContextService);
|
|
32
86
|
this.elementRef = inject(ElementRef);
|
|
33
87
|
this.destroy$ = new Subject();
|
|
88
|
+
// ============================================================
|
|
89
|
+
// Public Properties
|
|
90
|
+
// ============================================================
|
|
91
|
+
/**
|
|
92
|
+
* 實際設備方向(基於視口尺寸)
|
|
93
|
+
* true: 直向 (height > width)
|
|
94
|
+
* false: 橫向 (width >= height)
|
|
95
|
+
*/
|
|
96
|
+
this.actualIsPortrait = false;
|
|
34
97
|
}
|
|
98
|
+
// ============================================================
|
|
99
|
+
// Getters
|
|
100
|
+
// ============================================================
|
|
101
|
+
/**
|
|
102
|
+
* 判斷當前是否為橫向模式
|
|
103
|
+
* 根據 orientation 設定和實際設備方向判斷
|
|
104
|
+
*/
|
|
35
105
|
get isLandscape() {
|
|
106
|
+
// landscapeLockRotation: 強制橫屏模式
|
|
107
|
+
if (this.orientation === 'landscapeLockRotation') {
|
|
108
|
+
return true;
|
|
109
|
+
}
|
|
110
|
+
// portraitLockRotation: 強制直屏模式
|
|
111
|
+
if (this.orientation === 'portraitLockRotation') {
|
|
112
|
+
return false;
|
|
113
|
+
}
|
|
114
|
+
// landscape: 鎖定橫向
|
|
115
|
+
if (this.orientation === 'landscape') {
|
|
116
|
+
return true;
|
|
117
|
+
}
|
|
118
|
+
// portrait: 鎖定直向
|
|
119
|
+
if (this.orientation === 'portrait') {
|
|
120
|
+
return false;
|
|
121
|
+
}
|
|
122
|
+
// auto: 自動檢測
|
|
36
123
|
return this.boundaryContext.getIsLandscape();
|
|
37
124
|
}
|
|
125
|
+
// ============================================================
|
|
126
|
+
// Lifecycle Hooks
|
|
127
|
+
// ============================================================
|
|
38
128
|
ngOnInit() {
|
|
129
|
+
this.updateActualOrientation();
|
|
39
130
|
this.setupBreakpointObserver();
|
|
40
131
|
this.setupResizeObserver();
|
|
41
132
|
}
|
|
@@ -47,97 +138,183 @@ class BsBoundary {
|
|
|
47
138
|
this.destroy$.next();
|
|
48
139
|
this.destroy$.complete();
|
|
49
140
|
}
|
|
141
|
+
// ============================================================
|
|
142
|
+
// Observers Setup - 監聽器設定
|
|
143
|
+
// ============================================================
|
|
50
144
|
/**
|
|
51
|
-
*
|
|
52
|
-
*
|
|
145
|
+
* 設定方向變化監控
|
|
146
|
+
*
|
|
147
|
+
* 工作原理:
|
|
148
|
+
* - lock 模式(portrait/landscape):直接設定固定方向,不監聽變化
|
|
149
|
+
* - lockRotation 模式:設定固定方向,但需監聽實際設備方向以觸發旋轉
|
|
150
|
+
* - auto 模式:使用 BreakpointObserver 監聽設備方向變化
|
|
53
151
|
*/
|
|
54
152
|
setupBreakpointObserver() {
|
|
55
|
-
|
|
153
|
+
// landscape 或 landscapeLockRotation: 鎖定為橫向
|
|
154
|
+
if (this.orientation === 'landscape' || this.orientation === 'landscapeLockRotation') {
|
|
56
155
|
this.boundaryContext.setIsLandscape(true);
|
|
57
156
|
this.orientationChange.emit(true);
|
|
58
157
|
return;
|
|
59
158
|
}
|
|
60
|
-
|
|
159
|
+
// portrait 或 portraitLockRotation: 鎖定為直向
|
|
160
|
+
if (this.orientation === 'portrait' || this.orientation === 'portraitLockRotation') {
|
|
61
161
|
this.boundaryContext.setIsLandscape(false);
|
|
62
162
|
this.orientationChange.emit(false);
|
|
63
163
|
return;
|
|
64
164
|
}
|
|
65
|
-
//
|
|
165
|
+
// auto 模式:監聽實際的設備方向(用於 orientationChange 事件)
|
|
66
166
|
this.breakpointObserver
|
|
67
167
|
.observe('(orientation: landscape)')
|
|
68
168
|
.pipe(takeUntil(this.destroy$))
|
|
69
169
|
.subscribe(result => {
|
|
70
170
|
this.boundaryContext.setIsLandscape(result.matches);
|
|
71
171
|
this.orientationChange.emit(result.matches);
|
|
72
|
-
//
|
|
172
|
+
// 方向變化時重新計算邊界
|
|
73
173
|
this.updateBoundaryCalculations();
|
|
74
174
|
});
|
|
75
175
|
}
|
|
76
176
|
/**
|
|
77
|
-
*
|
|
177
|
+
* 設定視窗大小變化監控
|
|
178
|
+
*
|
|
179
|
+
* 功能:
|
|
180
|
+
* - 監聽 window resize 事件
|
|
181
|
+
* - 更新實際設備方向
|
|
182
|
+
* - 重新計算邊界尺寸
|
|
78
183
|
*/
|
|
79
184
|
setupResizeObserver() {
|
|
80
185
|
fromEvent(window, 'resize')
|
|
81
186
|
.pipe(
|
|
82
|
-
// debounceTime(150),
|
|
187
|
+
// debounceTime(150), // 可選:防抖優化性能
|
|
83
188
|
takeUntil(this.destroy$))
|
|
84
189
|
.subscribe(() => {
|
|
190
|
+
this.updateActualOrientation();
|
|
85
191
|
this.updateBoundaryCalculations();
|
|
86
192
|
});
|
|
87
193
|
}
|
|
194
|
+
// ============================================================
|
|
195
|
+
// Calculation Methods - 計算方法
|
|
196
|
+
// ============================================================
|
|
197
|
+
/**
|
|
198
|
+
* 更新實際設備方向(基於視口尺寸)
|
|
199
|
+
*
|
|
200
|
+
* 工作原理:
|
|
201
|
+
* - 只在 lockRotation 模式下需要檢測實際方向
|
|
202
|
+
* - 通過比較 window.innerHeight 和 window.innerWidth 判斷
|
|
203
|
+
* - 結果用於觸發 CSS 旋轉變換
|
|
204
|
+
*/
|
|
205
|
+
updateActualOrientation() {
|
|
206
|
+
// 只有在 lockRotation 模式下才需要檢測實際方向
|
|
207
|
+
if (this.orientation !== 'landscapeLockRotation' && this.orientation !== 'portraitLockRotation') {
|
|
208
|
+
this.actualIsPortrait = false;
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
// 檢測實際視口方向:高度 > 寬度 = 直向
|
|
212
|
+
this.actualIsPortrait = window.innerHeight > window.innerWidth;
|
|
213
|
+
}
|
|
88
214
|
/**
|
|
89
215
|
* 更新邊界計算(支援 Chrome 74,使用 JavaScript 模擬 min 函數)
|
|
90
|
-
*
|
|
216
|
+
*
|
|
217
|
+
* 工作原理:
|
|
218
|
+
* 1. 讀取基礎 CSS 變數(vw100, vh100, design-width, design-height)
|
|
219
|
+
* 2. 處理 lockRotation 模式的視口尺寸交換
|
|
220
|
+
* 3. 計算邊界最大值和實際邊界尺寸
|
|
221
|
+
* 4. 計算 px2vw 轉換比例
|
|
222
|
+
* 5. 設定 CSS 變數供樣式使用
|
|
223
|
+
*
|
|
224
|
+
* 設定的 CSS 變數:
|
|
225
|
+
* - --boundary-width: 實際邊界寬度(px)
|
|
226
|
+
* - --boundary-height: 實際邊界高度(px)
|
|
227
|
+
* - --px2vw-ratio: px 轉 vw 的比例係數
|
|
91
228
|
*/
|
|
92
229
|
updateBoundaryCalculations() {
|
|
93
230
|
const hostElement = this.elementRef.nativeElement;
|
|
94
231
|
const computedStyle = getComputedStyle(hostElement);
|
|
95
|
-
//
|
|
96
|
-
|
|
97
|
-
|
|
232
|
+
// ========================================
|
|
233
|
+
// Step 1: 讀取基礎 CSS 變數
|
|
234
|
+
// ========================================
|
|
235
|
+
let vw100 = parseFloat(computedStyle.getPropertyValue('--vw100')) || window.innerWidth;
|
|
236
|
+
let vh100 = parseFloat(computedStyle.getPropertyValue('--vh100')) || window.innerHeight;
|
|
98
237
|
const designWidth = parseFloat(computedStyle.getPropertyValue('--design-width'));
|
|
99
238
|
const designHeight = parseFloat(computedStyle.getPropertyValue('--design-height'));
|
|
100
|
-
console.log('designWidth', designWidth);
|
|
101
239
|
// 檢查基礎變數是否準備好
|
|
102
|
-
if (isNaN(designWidth)
|
|
103
|
-
|
|
240
|
+
if (isNaN(designWidth)) {
|
|
241
|
+
console.error('[BsBoundary][BS001] CSS variable --design-width is not set or invalid');
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
if (isNaN(designHeight)) {
|
|
245
|
+
console.error('[BsBoundary][BS002] CSS variable --design-height is not set or invalid');
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
if (designWidth === 0) {
|
|
249
|
+
console.error('[BsBoundary][BS003] CSS variable --design-width cannot be zero');
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
if (designHeight === 0) {
|
|
253
|
+
console.error('[BsBoundary][BS004] CSS variable --design-height cannot be zero');
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
// ========================================
|
|
257
|
+
// Step 2: 處理 lockRotation 模式的視口尺寸交換
|
|
258
|
+
// ========================================
|
|
259
|
+
// landscapeLockRotation + 實際直向:交換視口尺寸
|
|
260
|
+
// 原因:旋轉後,內容的寬度對應設備的高度,內容的高度對應設備的寬度
|
|
261
|
+
if (this.orientation === 'landscapeLockRotation' && this.actualIsPortrait) {
|
|
262
|
+
[vw100, vh100] = [vh100, vw100];
|
|
263
|
+
}
|
|
264
|
+
// portraitLockRotation + 實際橫向:交換視口尺寸
|
|
265
|
+
if (this.orientation === 'portraitLockRotation' && !this.actualIsPortrait) {
|
|
266
|
+
[vw100, vh100] = [vh100, vw100];
|
|
104
267
|
}
|
|
105
|
-
//
|
|
268
|
+
// ========================================
|
|
269
|
+
// Step 3: 計算比例和邊界最大值
|
|
270
|
+
// ========================================
|
|
106
271
|
const landscapeRatio = designWidth / designHeight;
|
|
107
272
|
const portraitRatio = designHeight / designWidth;
|
|
108
|
-
//
|
|
273
|
+
// 根據方向模式計算邊界最大值
|
|
109
274
|
const isLandscape = this.isLandscape;
|
|
110
275
|
const boundaryMaxWidth = isLandscape
|
|
111
|
-
? vh100 * portraitRatio
|
|
112
|
-
: vh100 * landscapeRatio;
|
|
276
|
+
? vh100 * portraitRatio // 橫向:高度 * 高寬比
|
|
277
|
+
: vh100 * landscapeRatio; // 直向:高度 * 寬高比
|
|
113
278
|
const boundaryMaxHeight = isLandscape
|
|
114
|
-
? vw100 * landscapeRatio
|
|
115
|
-
: vw100 * portraitRatio;
|
|
116
|
-
//
|
|
279
|
+
? vw100 * landscapeRatio // 橫向:寬度 * 寬高比
|
|
280
|
+
: vw100 * portraitRatio; // 直向:寬度 * 高寬比
|
|
281
|
+
// ========================================
|
|
282
|
+
// Step 4: 計算實際邊界尺寸(使用 Math.min 模擬 CSS min())
|
|
283
|
+
// ========================================
|
|
284
|
+
// Chrome 79 才支援 CSS min() 函數,所以在 JavaScript 中計算
|
|
117
285
|
const boundaryWidth = Math.min(vw100, boundaryMaxWidth);
|
|
118
286
|
const boundaryHeight = Math.min(vh100, boundaryMaxHeight);
|
|
119
|
-
//
|
|
287
|
+
// ========================================
|
|
288
|
+
// Step 5: 計算 px2vw 轉換比例
|
|
289
|
+
// ========================================
|
|
290
|
+
// 橫向模式使用 design-height 作為基準,直向模式使用 design-width 作為基準
|
|
120
291
|
const designBaseWidth = isLandscape ? designHeight : designWidth;
|
|
121
292
|
const px2vwRatio = boundaryWidth / designBaseWidth;
|
|
122
|
-
//
|
|
293
|
+
// ========================================
|
|
294
|
+
// Step 6: 設定 CSS 變數
|
|
295
|
+
// ========================================
|
|
123
296
|
hostElement.style.setProperty('--boundary-width', `${boundaryWidth}px`);
|
|
124
297
|
hostElement.style.setProperty('--boundary-height', `${boundaryHeight}px`);
|
|
125
298
|
hostElement.style.setProperty('--px2vw-ratio', `${px2vwRatio}`);
|
|
126
299
|
}
|
|
127
300
|
}
|
|
128
301
|
BsBoundary.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: BsBoundary, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
129
|
-
BsBoundary.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "14.3.0", type: BsBoundary, isStandalone: true, selector: "bs-boundary", inputs: { isFixedCenter: "isFixedCenter", pointerEventsNone: "pointerEventsNone",
|
|
302
|
+
BsBoundary.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "14.3.0", type: BsBoundary, isStandalone: true, selector: "bs-boundary", inputs: { isFixedCenter: "isFixedCenter", pointerEventsNone: "pointerEventsNone", orientation: "orientation", forwardStyle: "forwardStyle" }, outputs: { orientationChange: "orientationChange" }, host: { properties: { "class.landscape": "isLandscape && orientation === \"landscape\"", "class.portrait": "!isLandscape && orientation === \"portrait\"", "class.landscape-lock-rotation": "orientation === \"landscapeLockRotation\"", "class.portrait-lock-rotation": "orientation === \"portraitLockRotation\"", "class.actual-portrait": "actualIsPortrait", "class.actual-landscape": "!actualIsPortrait" } }, providers: [BsBoundaryContextService], ngImport: i0, template: "<div\n class=\"boundary-wrapper\"\n [class.fixed-center]=\"isFixedCenter\"\n [class.pointerEventsNone]=\"pointerEventsNone\"\n [style]=\"forwardStyle\"\n>\n <ng-content></ng-content>\n</div>\n", styles: [":host{--landscape-ratio: calc(var(--design-width) / var(--design-height));--portrait-ratio: calc(var(--design-height) / var(--design-width));--boundary-max-width: calc(var(--vh100) * var(--landscape-ratio));--boundary-max-height: calc(var(--vw100) * var(--portrait-ratio));--design-base-width: var(--design-width);--design-base-height: var(--design-height);--px2vw-ratio: calc(var(--vw100) / var(--design-base-width))}.boundary-wrapper{width:var(--vw100);height:var(--vh100);max-width:var(--boundary-max-width);max-height:var(--boundary-max-height)}.boundary-wrapper.pointerEventsNone{pointer-events:none}:host.portrait{--boundary-max-width: calc(var(--vh100) * var(--landscape-ratio));--boundary-max-height: calc(var(--vw100) * var(--portrait-ratio));--design-base-width: var(--design-width);--design-base-height: var(--design-height);--px2vw-ratio: calc(var(--vw100) / var(--design-base-width))}:host.landscape{--boundary-max-width: calc(var(--vh100) * var(--portrait-ratio));--boundary-max-height: calc(var(--vw100) * var(--landscape-ratio));--design-base-width: var(--design-height);--design-base-height: var(--design-width);--px2vw-ratio: calc(var(--vh100) * var(--portrait-ratio) / var(--design-base-width))}@media (orientation: landscape){:host:not(.landscape):not(.portrait):not(.landscape-lock-rotation):not(.portrait-lock-rotation){--boundary-max-width: calc(var(--vh100) * var(--portrait-ratio));--boundary-max-height: calc(var(--vw100) * var(--landscape-ratio));--design-base-width: var(--design-height);--design-base-height: var(--design-width);--px2vw-ratio: calc(var(--vh100) * var(--portrait-ratio) / var(--design-base-width))}}.fixed-center{position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);transform-origin:center;z-index:99}:host.landscape-lock-rotation{--boundary-max-width: calc(var(--vh100) * var(--portrait-ratio));--boundary-max-height: calc(var(--vw100) * var(--landscape-ratio));--design-base-width: var(--design-height);--design-base-height: var(--design-width);--px2vw-ratio: calc(var(--vh100) * var(--portrait-ratio) / var(--design-base-width))}:host.landscape-lock-rotation.actual-portrait .boundary-wrapper{position:fixed;top:50%;left:50%;transform:translate(-50%,-50%) rotate(90deg);transform-origin:center;width:var(--boundary-width);height:var(--boundary-height);max-width:none;max-height:none}:host.portrait-lock-rotation{--boundary-max-width: calc(var(--vh100) * var(--landscape-ratio));--boundary-max-height: calc(var(--vw100) * var(--portrait-ratio));--design-base-width: var(--design-width);--design-base-height: var(--design-height);--px2vw-ratio: calc(var(--vw100) / var(--design-base-width))}:host.portrait-lock-rotation.actual-landscape .boundary-wrapper{position:fixed;top:50%;left:50%;transform:translate(-50%,-50%) rotate(-90deg);transform-origin:center;width:var(--boundary-width);height:var(--boundary-height);max-width:none;max-height:none}\n"] });
|
|
130
303
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: BsBoundary, decorators: [{
|
|
131
304
|
type: Component,
|
|
132
305
|
args: [{ selector: 'bs-boundary', standalone: true, providers: [BsBoundaryContextService], host: {
|
|
133
|
-
'[class.landscape]': 'isLandscape &&
|
|
134
|
-
'[class.portrait]': '!isLandscape &&
|
|
135
|
-
|
|
306
|
+
'[class.landscape]': 'isLandscape && orientation === "landscape"',
|
|
307
|
+
'[class.portrait]': '!isLandscape && orientation === "portrait"',
|
|
308
|
+
'[class.landscape-lock-rotation]': 'orientation === "landscapeLockRotation"',
|
|
309
|
+
'[class.portrait-lock-rotation]': 'orientation === "portraitLockRotation"',
|
|
310
|
+
'[class.actual-portrait]': 'actualIsPortrait',
|
|
311
|
+
'[class.actual-landscape]': '!actualIsPortrait'
|
|
312
|
+
}, template: "<div\n class=\"boundary-wrapper\"\n [class.fixed-center]=\"isFixedCenter\"\n [class.pointerEventsNone]=\"pointerEventsNone\"\n [style]=\"forwardStyle\"\n>\n <ng-content></ng-content>\n</div>\n", styles: [":host{--landscape-ratio: calc(var(--design-width) / var(--design-height));--portrait-ratio: calc(var(--design-height) / var(--design-width));--boundary-max-width: calc(var(--vh100) * var(--landscape-ratio));--boundary-max-height: calc(var(--vw100) * var(--portrait-ratio));--design-base-width: var(--design-width);--design-base-height: var(--design-height);--px2vw-ratio: calc(var(--vw100) / var(--design-base-width))}.boundary-wrapper{width:var(--vw100);height:var(--vh100);max-width:var(--boundary-max-width);max-height:var(--boundary-max-height)}.boundary-wrapper.pointerEventsNone{pointer-events:none}:host.portrait{--boundary-max-width: calc(var(--vh100) * var(--landscape-ratio));--boundary-max-height: calc(var(--vw100) * var(--portrait-ratio));--design-base-width: var(--design-width);--design-base-height: var(--design-height);--px2vw-ratio: calc(var(--vw100) / var(--design-base-width))}:host.landscape{--boundary-max-width: calc(var(--vh100) * var(--portrait-ratio));--boundary-max-height: calc(var(--vw100) * var(--landscape-ratio));--design-base-width: var(--design-height);--design-base-height: var(--design-width);--px2vw-ratio: calc(var(--vh100) * var(--portrait-ratio) / var(--design-base-width))}@media (orientation: landscape){:host:not(.landscape):not(.portrait):not(.landscape-lock-rotation):not(.portrait-lock-rotation){--boundary-max-width: calc(var(--vh100) * var(--portrait-ratio));--boundary-max-height: calc(var(--vw100) * var(--landscape-ratio));--design-base-width: var(--design-height);--design-base-height: var(--design-width);--px2vw-ratio: calc(var(--vh100) * var(--portrait-ratio) / var(--design-base-width))}}.fixed-center{position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);transform-origin:center;z-index:99}:host.landscape-lock-rotation{--boundary-max-width: calc(var(--vh100) * var(--portrait-ratio));--boundary-max-height: calc(var(--vw100) * var(--landscape-ratio));--design-base-width: var(--design-height);--design-base-height: var(--design-width);--px2vw-ratio: calc(var(--vh100) * var(--portrait-ratio) / var(--design-base-width))}:host.landscape-lock-rotation.actual-portrait .boundary-wrapper{position:fixed;top:50%;left:50%;transform:translate(-50%,-50%) rotate(90deg);transform-origin:center;width:var(--boundary-width);height:var(--boundary-height);max-width:none;max-height:none}:host.portrait-lock-rotation{--boundary-max-width: calc(var(--vh100) * var(--landscape-ratio));--boundary-max-height: calc(var(--vw100) * var(--portrait-ratio));--design-base-width: var(--design-width);--design-base-height: var(--design-height);--px2vw-ratio: calc(var(--vw100) / var(--design-base-width))}:host.portrait-lock-rotation.actual-landscape .boundary-wrapper{position:fixed;top:50%;left:50%;transform:translate(-50%,-50%) rotate(-90deg);transform-origin:center;width:var(--boundary-width);height:var(--boundary-height);max-width:none;max-height:none}\n"] }]
|
|
136
313
|
}], propDecorators: { isFixedCenter: [{
|
|
137
314
|
type: Input
|
|
138
315
|
}], pointerEventsNone: [{
|
|
139
316
|
type: Input
|
|
140
|
-
}],
|
|
317
|
+
}], orientation: [{
|
|
141
318
|
type: Input
|
|
142
319
|
}], forwardStyle: [{
|
|
143
320
|
type: Input
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bitstack-ng-boundary.mjs","sources":["../../../projects/bitstack-ng-boundary/src/lib/bs-boundary-context.service.ts","../../../projects/bitstack-ng-boundary/src/lib/bs-boundary.ts","../../../projects/bitstack-ng-boundary/src/lib/template.html","../../../projects/bitstack-ng-boundary/src/public-api.ts","../../../projects/bitstack-ng-boundary/src/bitstack-ng-boundary.ts"],"sourcesContent":["import { Injectable } from '@angular/core';\nimport { BehaviorSubject } from 'rxjs';\n\n@Injectable()\nexport class BsBoundaryContextService {\n private _isLandscape = new BehaviorSubject<boolean>(false);\n\n readonly isLandscape$ = this._isLandscape.asObservable();\n\n setIsLandscape(value: boolean): void {\n this._isLandscape.next(value);\n }\n\n getIsLandscape(): boolean {\n return this._isLandscape.value;\n }\n}\n","import {\n AfterViewInit,\n Component,\n ElementRef,\n EventEmitter,\n inject,\n Input, OnDestroy,\n OnInit,\n Output,\n} from '@angular/core';\nimport {TOrientationMode} from './model';\nimport {BreakpointObserver} from '@angular/cdk/layout';\nimport {debounceTime, fromEvent, Subject, takeUntil} from 'rxjs';\nimport {BsBoundaryContextService} from './bs-boundary-context.service';\n\n@Component({\n selector: 'bs-boundary',\n templateUrl: 'template.html',\n standalone: true,\n styleUrls: ['./styles.scss'],\n providers: [BsBoundaryContextService],\n host: {\n '[class.landscape]': 'isLandscape && lockOrientation !== \"auto\"',\n '[class.portrait]': '!isLandscape && lockOrientation !== \"auto\"'\n }\n})\nexport class BsBoundary implements OnInit, AfterViewInit, OnDestroy {\n\n @Input() isFixedCenter?: boolean = false;\n @Input() pointerEventsNone?: boolean = false;\n @Input() lockOrientation?: TOrientationMode = 'auto';\n @Input() forwardStyle?: Record<string, string>;\n\n @Output() orientationChange = new EventEmitter<boolean>();\n\n private breakpointObserver = inject(BreakpointObserver);\n private boundaryContext = inject(BsBoundaryContextService);\n private elementRef = inject(ElementRef);\n private destroy$ = new Subject<void>();\n\n get isLandscape(): boolean {\n return this.boundaryContext.getIsLandscape();\n }\n\n ngOnInit(): void {\n this.setupBreakpointObserver();\n this.setupResizeObserver();\n }\n\n ngAfterViewInit(): void {\n // 在 view 初始化後計算(確保 CSS 已完全應用)\n this.updateBoundaryCalculations();\n }\n\n ngOnDestroy(): void {\n this.destroy$.next();\n this.destroy$.complete();\n }\n\n /**\n * 監控方向變化\n * 只在 lock 模式時需要,auto 模式由 CSS 媒體查詢處理\n */\n setupBreakpointObserver() {\n if (this.lockOrientation === 'landscape') {\n this.boundaryContext.setIsLandscape(true);\n this.orientationChange.emit(true);\n return;\n }\n\n if (this.lockOrientation === 'portrait') {\n this.boundaryContext.setIsLandscape(false);\n this.orientationChange.emit(false);\n return;\n }\n\n // Auto 模式:監聽實際的設備方向(用於 orientationChange 事件)\n this.breakpointObserver\n .observe('(orientation: landscape)')\n .pipe(takeUntil(this.destroy$))\n .subscribe(result => {\n this.boundaryContext.setIsLandscape(result.matches);\n this.orientationChange.emit(result.matches);\n // 方向變化時重新計算\n this.updateBoundaryCalculations();\n });\n }\n\n /**\n * 監控視口大小變化\n */\n setupResizeObserver() {\n fromEvent(window, 'resize')\n .pipe(\n // debounceTime(150),\n takeUntil(this.destroy$)\n )\n .subscribe(() => {\n this.updateBoundaryCalculations();\n });\n }\n\n /**\n * 更新邊界計算(支援 Chrome 74,使用 JavaScript 模擬 min 函數)\n * 設定 --boundary-width, --boundary-height, --px2vw-ratio\n */\n updateBoundaryCalculations() {\n const hostElement = this.elementRef.nativeElement as HTMLElement;\n const computedStyle = getComputedStyle(hostElement);\n\n // 讀取基礎 CSS 變數(這些是純數值,不是 calc() 表達式)\n const vw100 = parseFloat(computedStyle.getPropertyValue('--vw100')) || window.innerWidth;\n const vh100 = parseFloat(computedStyle.getPropertyValue('--vh100')) || window.innerHeight;\n const designWidth = parseFloat(computedStyle.getPropertyValue('--design-width'));\n const designHeight = parseFloat(computedStyle.getPropertyValue('--design-height'));\n\n console.log('designWidth', designWidth);\n\n // 檢查基礎變數是否準備好\n if (isNaN(designWidth) || isNaN(designHeight) || designWidth === 0 || designHeight === 0) {\n return; // CSS 變數還沒準備好,跳過\n }\n\n // 在 JavaScript 中計算所有值(避免讀取 calc() 表達式)\n const landscapeRatio = designWidth / designHeight;\n const portraitRatio = designHeight / designWidth;\n\n // 根據方向計算邊界最大值\n const isLandscape = this.isLandscape;\n const boundaryMaxWidth = isLandscape\n ? vh100 * portraitRatio\n : vh100 * landscapeRatio;\n const boundaryMaxHeight = isLandscape\n ? vw100 * landscapeRatio\n : vw100 * portraitRatio;\n\n // 使用 JavaScript Math.min 模擬 CSS min() 函數\n const boundaryWidth = Math.min(vw100, boundaryMaxWidth);\n const boundaryHeight = Math.min(vh100, boundaryMaxHeight);\n\n // 計算設計基準和 px2vw 比例\n const designBaseWidth = isLandscape ? designHeight : designWidth;\n const px2vwRatio = boundaryWidth / designBaseWidth;\n\n // 設定 CSS 變數(向後兼容 + 精確的 ratio)\n hostElement.style.setProperty('--boundary-width', `${boundaryWidth}px`);\n hostElement.style.setProperty('--boundary-height', `${boundaryHeight}px`);\n hostElement.style.setProperty('--px2vw-ratio', `${px2vwRatio}`);\n }\n}\n","<div\n class=\"boundary-wrapper\"\n [class.fixed-center]=\"isFixedCenter\"\n [class.pointerEventsNone]=\"pointerEventsNone\"\n [style]=\"forwardStyle\"\n>\n <ng-content></ng-content>\n</div>\n","/*\n * Public API Surface of @bitstack/ng-boundary\n */\n\nexport * from './lib/bs-boundary';\nexport * from './lib/bs-boundary-context.service';\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"bitstack-ng-boundary.mjs","sources":["../../../projects/bitstack-ng-boundary/src/lib/bs-boundary-context.service.ts","../../../projects/bitstack-ng-boundary/src/lib/bs-boundary.ts","../../../projects/bitstack-ng-boundary/src/lib/template.html","../../../projects/bitstack-ng-boundary/src/public-api.ts","../../../projects/bitstack-ng-boundary/src/bitstack-ng-boundary.ts"],"sourcesContent":["import {Injectable} from '@angular/core';\nimport {BehaviorSubject} from 'rxjs';\n\n@Injectable()\nexport class BsBoundaryContextService {\n private _isLandscape = new BehaviorSubject<boolean>(false);\n\n readonly isLandscape$ = this._isLandscape.asObservable();\n\n setIsLandscape(value: boolean): void {\n this._isLandscape.next(value);\n }\n\n getIsLandscape(): boolean {\n return this._isLandscape.value;\n }\n}\n","import {BreakpointObserver} from '@angular/cdk/layout';\nimport {\n AfterViewInit,\n Component,\n ElementRef,\n EventEmitter,\n inject,\n Input, OnDestroy,\n OnInit,\n Output,\n} from '@angular/core';\nimport {debounceTime, fromEvent, Subject, takeUntil} from 'rxjs';\n\nimport {BsBoundaryContextService} from './bs-boundary-context.service';\nimport {TOrientationMode} from './model';\n\n/**\n * ============================================================\n * BsBoundary Component - AWD 自適應邊界容器\n * ============================================================\n *\n * 功能說明:\n * 1. 提供響應式邊界容器,自動適應不同設備尺寸\n * 2. 支援 5 種方向模式:auto, portrait, landscape, portraitLockRotation, landscapeLockRotation\n * 3. 使用 CSS 變數實現動態計算,支援 px2vw() 函數\n * 4. 相容 Chrome 74+,使用 JavaScript 計算邊界尺寸\n *\n * 使用範例:\n * ```html\n * <bs-boundary [orientation]=\"'auto'\" [isFixedCenter]=\"true\">\n * <div class=\"content\">...</div>\n * </bs-boundary>\n * ```\n *\n * CSS 變數設定:\n * ```scss\n * bs-boundary {\n * --design-width: 375; // 設計稿寬度\n * --design-height: 667; // 設計稿高度\n * }\n * ```\n */\n@Component({\n selector: 'bs-boundary',\n templateUrl: './template.html',\n standalone: true,\n styleUrls: ['./styles.scss'],\n providers: [BsBoundaryContextService],\n host: {\n '[class.landscape]': 'isLandscape && orientation === \"landscape\"',\n '[class.portrait]': '!isLandscape && orientation === \"portrait\"',\n '[class.landscape-lock-rotation]': 'orientation === \"landscapeLockRotation\"',\n '[class.portrait-lock-rotation]': 'orientation === \"portraitLockRotation\"',\n '[class.actual-portrait]': 'actualIsPortrait',\n '[class.actual-landscape]': '!actualIsPortrait'\n }\n})\nexport class BsBoundary implements OnInit, AfterViewInit, OnDestroy {\n\n // ============================================================\n // Inputs & Outputs\n // ============================================================\n\n /**\n * 是否將內容固定在視窗中央\n * @default false\n */\n @Input() isFixedCenter?: boolean = false;\n\n /**\n * 是否禁用指標事件\n * @default false\n */\n @Input() pointerEventsNone?: boolean = false;\n\n /**\n * 方向模式\n * - auto: 自動檢測方向\n * - portrait: 鎖定直向\n * - landscape: 鎖定橫向\n * - portraitLockRotation: 強制直向(橫向設備時旋轉 -90°)\n * - landscapeLockRotation: 強制橫向(直向設備時旋轉 90°)\n * @default 'auto'\n */\n @Input() orientation?: TOrientationMode = 'auto';\n\n /**\n * 向內層容器轉發的自訂樣式\n */\n @Input() forwardStyle?: Record<string, string>;\n\n /**\n * 方向變化事件\n * @emits true: landscape(橫屏)\n * @emits false: portrait(直屏)\n */\n @Output() orientationChange = new EventEmitter<boolean>();\n\n // ============================================================\n // Private Dependencies\n // ============================================================\n\n private breakpointObserver = inject(BreakpointObserver);\n private boundaryContext = inject(BsBoundaryContextService);\n private elementRef = inject(ElementRef);\n private destroy$ = new Subject<void>();\n\n // ============================================================\n // Public Properties\n // ============================================================\n\n /**\n * 實際設備方向(基於視口尺寸)\n * true: 直向 (height > width)\n * false: 橫向 (width >= height)\n */\n actualIsPortrait = false;\n\n // ============================================================\n // Getters\n // ============================================================\n\n /**\n * 判斷當前是否為橫向模式\n * 根據 orientation 設定和實際設備方向判斷\n */\n get isLandscape(): boolean {\n // landscapeLockRotation: 強制橫屏模式\n if (this.orientation === 'landscapeLockRotation') {\n return true;\n }\n // portraitLockRotation: 強制直屏模式\n if (this.orientation === 'portraitLockRotation') {\n return false;\n }\n // landscape: 鎖定橫向\n if (this.orientation === 'landscape') {\n return true;\n }\n // portrait: 鎖定直向\n if (this.orientation === 'portrait') {\n return false;\n }\n // auto: 自動檢測\n return this.boundaryContext.getIsLandscape();\n }\n\n // ============================================================\n // Lifecycle Hooks\n // ============================================================\n\n ngOnInit(): void {\n this.updateActualOrientation();\n this.setupBreakpointObserver();\n this.setupResizeObserver();\n }\n\n ngAfterViewInit(): void {\n // 在 view 初始化後計算(確保 CSS 已完全應用)\n this.updateBoundaryCalculations();\n }\n\n ngOnDestroy(): void {\n this.destroy$.next();\n this.destroy$.complete();\n }\n\n // ============================================================\n // Observers Setup - 監聽器設定\n // ============================================================\n\n /**\n * 設定方向變化監控\n *\n * 工作原理:\n * - lock 模式(portrait/landscape):直接設定固定方向,不監聽變化\n * - lockRotation 模式:設定固定方向,但需監聽實際設備方向以觸發旋轉\n * - auto 模式:使用 BreakpointObserver 監聽設備方向變化\n */\n private setupBreakpointObserver() {\n // landscape 或 landscapeLockRotation: 鎖定為橫向\n if (this.orientation === 'landscape' || this.orientation === 'landscapeLockRotation') {\n this.boundaryContext.setIsLandscape(true);\n this.orientationChange.emit(true);\n return;\n }\n\n // portrait 或 portraitLockRotation: 鎖定為直向\n if (this.orientation === 'portrait' || this.orientation === 'portraitLockRotation') {\n this.boundaryContext.setIsLandscape(false);\n this.orientationChange.emit(false);\n return;\n }\n\n // auto 模式:監聽實際的設備方向(用於 orientationChange 事件)\n this.breakpointObserver\n .observe('(orientation: landscape)')\n .pipe(takeUntil(this.destroy$))\n .subscribe(result => {\n this.boundaryContext.setIsLandscape(result.matches);\n this.orientationChange.emit(result.matches);\n // 方向變化時重新計算邊界\n this.updateBoundaryCalculations();\n });\n }\n\n /**\n * 設定視窗大小變化監控\n *\n * 功能:\n * - 監聽 window resize 事件\n * - 更新實際設備方向\n * - 重新計算邊界尺寸\n */\n private setupResizeObserver() {\n fromEvent(window, 'resize')\n .pipe(\n // debounceTime(150), // 可選:防抖優化性能\n takeUntil(this.destroy$)\n )\n .subscribe(() => {\n this.updateActualOrientation();\n this.updateBoundaryCalculations();\n });\n }\n\n // ============================================================\n // Calculation Methods - 計算方法\n // ============================================================\n\n /**\n * 更新實際設備方向(基於視口尺寸)\n *\n * 工作原理:\n * - 只在 lockRotation 模式下需要檢測實際方向\n * - 通過比較 window.innerHeight 和 window.innerWidth 判斷\n * - 結果用於觸發 CSS 旋轉變換\n */\n private updateActualOrientation() {\n // 只有在 lockRotation 模式下才需要檢測實際方向\n if (this.orientation !== 'landscapeLockRotation' && this.orientation !== 'portraitLockRotation') {\n this.actualIsPortrait = false;\n return;\n }\n\n // 檢測實際視口方向:高度 > 寬度 = 直向\n this.actualIsPortrait = window.innerHeight > window.innerWidth;\n }\n\n /**\n * 更新邊界計算(支援 Chrome 74,使用 JavaScript 模擬 min 函數)\n *\n * 工作原理:\n * 1. 讀取基礎 CSS 變數(vw100, vh100, design-width, design-height)\n * 2. 處理 lockRotation 模式的視口尺寸交換\n * 3. 計算邊界最大值和實際邊界尺寸\n * 4. 計算 px2vw 轉換比例\n * 5. 設定 CSS 變數供樣式使用\n *\n * 設定的 CSS 變數:\n * - --boundary-width: 實際邊界寬度(px)\n * - --boundary-height: 實際邊界高度(px)\n * - --px2vw-ratio: px 轉 vw 的比例係數\n */\n private updateBoundaryCalculations() {\n const hostElement = this.elementRef.nativeElement as HTMLElement;\n const computedStyle = getComputedStyle(hostElement);\n\n // ========================================\n // Step 1: 讀取基礎 CSS 變數\n // ========================================\n let vw100 = parseFloat(computedStyle.getPropertyValue('--vw100')) || window.innerWidth;\n let vh100 = parseFloat(computedStyle.getPropertyValue('--vh100')) || window.innerHeight;\n const designWidth = parseFloat(computedStyle.getPropertyValue('--design-width'));\n const designHeight = parseFloat(computedStyle.getPropertyValue('--design-height'));\n\n // 檢查基礎變數是否準備好\n if (isNaN(designWidth)) {\n console.error('[BsBoundary][BS001] CSS variable --design-width is not set or invalid');\n return;\n }\n if (isNaN(designHeight)) {\n console.error('[BsBoundary][BS002] CSS variable --design-height is not set or invalid');\n return;\n }\n if (designWidth === 0) {\n console.error('[BsBoundary][BS003] CSS variable --design-width cannot be zero');\n return;\n }\n if (designHeight === 0) {\n console.error('[BsBoundary][BS004] CSS variable --design-height cannot be zero');\n return;\n }\n\n // ========================================\n // Step 2: 處理 lockRotation 模式的視口尺寸交換\n // ========================================\n // landscapeLockRotation + 實際直向:交換視口尺寸\n // 原因:旋轉後,內容的寬度對應設備的高度,內容的高度對應設備的寬度\n if (this.orientation === 'landscapeLockRotation' && this.actualIsPortrait) {\n [vw100, vh100] = [vh100, vw100];\n }\n\n // portraitLockRotation + 實際橫向:交換視口尺寸\n if (this.orientation === 'portraitLockRotation' && !this.actualIsPortrait) {\n [vw100, vh100] = [vh100, vw100];\n }\n\n // ========================================\n // Step 3: 計算比例和邊界最大值\n // ========================================\n const landscapeRatio = designWidth / designHeight;\n const portraitRatio = designHeight / designWidth;\n\n // 根據方向模式計算邊界最大值\n const isLandscape = this.isLandscape;\n const boundaryMaxWidth = isLandscape\n ? vh100 * portraitRatio // 橫向:高度 * 高寬比\n : vh100 * landscapeRatio; // 直向:高度 * 寬高比\n const boundaryMaxHeight = isLandscape\n ? vw100 * landscapeRatio // 橫向:寬度 * 寬高比\n : vw100 * portraitRatio; // 直向:寬度 * 高寬比\n\n // ========================================\n // Step 4: 計算實際邊界尺寸(使用 Math.min 模擬 CSS min())\n // ========================================\n // Chrome 79 才支援 CSS min() 函數,所以在 JavaScript 中計算\n const boundaryWidth = Math.min(vw100, boundaryMaxWidth);\n const boundaryHeight = Math.min(vh100, boundaryMaxHeight);\n\n // ========================================\n // Step 5: 計算 px2vw 轉換比例\n // ========================================\n // 橫向模式使用 design-height 作為基準,直向模式使用 design-width 作為基準\n const designBaseWidth = isLandscape ? designHeight : designWidth;\n const px2vwRatio = boundaryWidth / designBaseWidth;\n\n // ========================================\n // Step 6: 設定 CSS 變數\n // ========================================\n hostElement.style.setProperty('--boundary-width', `${boundaryWidth}px`);\n hostElement.style.setProperty('--boundary-height', `${boundaryHeight}px`);\n hostElement.style.setProperty('--px2vw-ratio', `${px2vwRatio}`);\n }\n}\n","<div\n class=\"boundary-wrapper\"\n [class.fixed-center]=\"isFixedCenter\"\n [class.pointerEventsNone]=\"pointerEventsNone\"\n [style]=\"forwardStyle\"\n>\n <ng-content></ng-content>\n</div>\n","/*\n * Public API Surface of @bitstack/ng-boundary\n */\n\nexport * from './lib/bs-boundary';\nexport * from './lib/bs-boundary-context.service';\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
|
package/lib/bs-boundary.d.ts
CHANGED
|
@@ -1,34 +1,124 @@
|
|
|
1
1
|
import { AfterViewInit, EventEmitter, OnDestroy, OnInit } from '@angular/core';
|
|
2
2
|
import { TOrientationMode } from './model';
|
|
3
3
|
import * as i0 from "@angular/core";
|
|
4
|
+
/**
|
|
5
|
+
* ============================================================
|
|
6
|
+
* BsBoundary Component - AWD 自適應邊界容器
|
|
7
|
+
* ============================================================
|
|
8
|
+
*
|
|
9
|
+
* 功能說明:
|
|
10
|
+
* 1. 提供響應式邊界容器,自動適應不同設備尺寸
|
|
11
|
+
* 2. 支援 5 種方向模式:auto, portrait, landscape, portraitLockRotation, landscapeLockRotation
|
|
12
|
+
* 3. 使用 CSS 變數實現動態計算,支援 px2vw() 函數
|
|
13
|
+
* 4. 相容 Chrome 74+,使用 JavaScript 計算邊界尺寸
|
|
14
|
+
*
|
|
15
|
+
* 使用範例:
|
|
16
|
+
* ```html
|
|
17
|
+
* <bs-boundary [orientation]="'auto'" [isFixedCenter]="true">
|
|
18
|
+
* <div class="content">...</div>
|
|
19
|
+
* </bs-boundary>
|
|
20
|
+
* ```
|
|
21
|
+
*
|
|
22
|
+
* CSS 變數設定:
|
|
23
|
+
* ```scss
|
|
24
|
+
* bs-boundary {
|
|
25
|
+
* --design-width: 375; // 設計稿寬度
|
|
26
|
+
* --design-height: 667; // 設計稿高度
|
|
27
|
+
* }
|
|
28
|
+
* ```
|
|
29
|
+
*/
|
|
4
30
|
export declare class BsBoundary implements OnInit, AfterViewInit, OnDestroy {
|
|
31
|
+
/**
|
|
32
|
+
* 是否將內容固定在視窗中央
|
|
33
|
+
* @default false
|
|
34
|
+
*/
|
|
5
35
|
isFixedCenter?: boolean;
|
|
36
|
+
/**
|
|
37
|
+
* 是否禁用指標事件
|
|
38
|
+
* @default false
|
|
39
|
+
*/
|
|
6
40
|
pointerEventsNone?: boolean;
|
|
7
|
-
|
|
41
|
+
/**
|
|
42
|
+
* 方向模式
|
|
43
|
+
* - auto: 自動檢測方向
|
|
44
|
+
* - portrait: 鎖定直向
|
|
45
|
+
* - landscape: 鎖定橫向
|
|
46
|
+
* - portraitLockRotation: 強制直向(橫向設備時旋轉 -90°)
|
|
47
|
+
* - landscapeLockRotation: 強制橫向(直向設備時旋轉 90°)
|
|
48
|
+
* @default 'auto'
|
|
49
|
+
*/
|
|
50
|
+
orientation?: TOrientationMode;
|
|
51
|
+
/**
|
|
52
|
+
* 向內層容器轉發的自訂樣式
|
|
53
|
+
*/
|
|
8
54
|
forwardStyle?: Record<string, string>;
|
|
55
|
+
/**
|
|
56
|
+
* 方向變化事件
|
|
57
|
+
* @emits true: landscape(橫屏)
|
|
58
|
+
* @emits false: portrait(直屏)
|
|
59
|
+
*/
|
|
9
60
|
orientationChange: EventEmitter<boolean>;
|
|
10
61
|
private breakpointObserver;
|
|
11
62
|
private boundaryContext;
|
|
12
63
|
private elementRef;
|
|
13
64
|
private destroy$;
|
|
65
|
+
/**
|
|
66
|
+
* 實際設備方向(基於視口尺寸)
|
|
67
|
+
* true: 直向 (height > width)
|
|
68
|
+
* false: 橫向 (width >= height)
|
|
69
|
+
*/
|
|
70
|
+
actualIsPortrait: boolean;
|
|
71
|
+
/**
|
|
72
|
+
* 判斷當前是否為橫向模式
|
|
73
|
+
* 根據 orientation 設定和實際設備方向判斷
|
|
74
|
+
*/
|
|
14
75
|
get isLandscape(): boolean;
|
|
15
76
|
ngOnInit(): void;
|
|
16
77
|
ngAfterViewInit(): void;
|
|
17
78
|
ngOnDestroy(): void;
|
|
18
79
|
/**
|
|
19
|
-
*
|
|
20
|
-
*
|
|
80
|
+
* 設定方向變化監控
|
|
81
|
+
*
|
|
82
|
+
* 工作原理:
|
|
83
|
+
* - lock 模式(portrait/landscape):直接設定固定方向,不監聽變化
|
|
84
|
+
* - lockRotation 模式:設定固定方向,但需監聽實際設備方向以觸發旋轉
|
|
85
|
+
* - auto 模式:使用 BreakpointObserver 監聽設備方向變化
|
|
86
|
+
*/
|
|
87
|
+
private setupBreakpointObserver;
|
|
88
|
+
/**
|
|
89
|
+
* 設定視窗大小變化監控
|
|
90
|
+
*
|
|
91
|
+
* 功能:
|
|
92
|
+
* - 監聽 window resize 事件
|
|
93
|
+
* - 更新實際設備方向
|
|
94
|
+
* - 重新計算邊界尺寸
|
|
21
95
|
*/
|
|
22
|
-
|
|
96
|
+
private setupResizeObserver;
|
|
23
97
|
/**
|
|
24
|
-
*
|
|
98
|
+
* 更新實際設備方向(基於視口尺寸)
|
|
99
|
+
*
|
|
100
|
+
* 工作原理:
|
|
101
|
+
* - 只在 lockRotation 模式下需要檢測實際方向
|
|
102
|
+
* - 通過比較 window.innerHeight 和 window.innerWidth 判斷
|
|
103
|
+
* - 結果用於觸發 CSS 旋轉變換
|
|
25
104
|
*/
|
|
26
|
-
|
|
105
|
+
private updateActualOrientation;
|
|
27
106
|
/**
|
|
28
107
|
* 更新邊界計算(支援 Chrome 74,使用 JavaScript 模擬 min 函數)
|
|
29
|
-
*
|
|
108
|
+
*
|
|
109
|
+
* 工作原理:
|
|
110
|
+
* 1. 讀取基礎 CSS 變數(vw100, vh100, design-width, design-height)
|
|
111
|
+
* 2. 處理 lockRotation 模式的視口尺寸交換
|
|
112
|
+
* 3. 計算邊界最大值和實際邊界尺寸
|
|
113
|
+
* 4. 計算 px2vw 轉換比例
|
|
114
|
+
* 5. 設定 CSS 變數供樣式使用
|
|
115
|
+
*
|
|
116
|
+
* 設定的 CSS 變數:
|
|
117
|
+
* - --boundary-width: 實際邊界寬度(px)
|
|
118
|
+
* - --boundary-height: 實際邊界高度(px)
|
|
119
|
+
* - --px2vw-ratio: px 轉 vw 的比例係數
|
|
30
120
|
*/
|
|
31
|
-
updateBoundaryCalculations
|
|
121
|
+
private updateBoundaryCalculations;
|
|
32
122
|
static ɵfac: i0.ɵɵFactoryDeclaration<BsBoundary, never>;
|
|
33
|
-
static ɵcmp: i0.ɵɵComponentDeclaration<BsBoundary, "bs-boundary", never, { "isFixedCenter": "isFixedCenter"; "pointerEventsNone": "pointerEventsNone"; "
|
|
123
|
+
static ɵcmp: i0.ɵɵComponentDeclaration<BsBoundary, "bs-boundary", never, { "isFixedCenter": "isFixedCenter"; "pointerEventsNone": "pointerEventsNone"; "orientation": "orientation"; "forwardStyle": "forwardStyle"; }, { "orientationChange": "orientationChange"; }, never, ["*"], true>;
|
|
34
124
|
}
|
package/lib/model.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare type TOrientationMode = 'auto' | 'portrait' | 'landscape';
|
|
1
|
+
export declare type TOrientationMode = 'auto' | 'portrait' | 'landscape' | 'landscapeLockRotation' | 'portraitLockRotation';
|