@mpgd/target-config 0.3.3 → 0.4.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.
package/dist/index.d.ts CHANGED
@@ -2,5 +2,6 @@ import type { EffectiveTargetConfigMatrix } from './effective';
2
2
  import type { TargetConfigMatrix } from './runtime';
3
3
  export * from './effective';
4
4
  export * from './runtime';
5
+ export * from './viewport';
5
6
  export declare const assertEffectiveTargetConfigMatrix: (input: unknown) => EffectiveTargetConfigMatrix;
6
7
  export declare const assertTargetConfigMatrix: (input: unknown) => TargetConfigMatrix;
package/dist/index.js CHANGED
@@ -4,6 +4,7 @@ const __typia_transform__accessExpressionAsString = _b._accessExpressionAsString
4
4
  import typia from 'typia';
5
5
  export * from './effective';
6
6
  export * from './runtime';
7
+ export * from './viewport';
7
8
  export const assertEffectiveTargetConfigMatrix = (() => {
8
9
  const _ip0 = input => "string" === typeof input["version"];
9
10
  const _ip1 = input => "boolean" === typeof input["iap"];
@@ -0,0 +1,69 @@
1
+ import type { TargetConfig, TargetRuntimeKind } from './runtime';
2
+ export type TargetViewportOrientation = 'portrait' | 'landscape';
3
+ export type TargetViewportOrientationPolicyMode = 'responsive' | 'prefer-landscape' | 'prefer-portrait' | 'lock-landscape' | 'lock-portrait';
4
+ export type TargetViewportOrientationMismatchBehavior = 'continue' | 'letterbox' | 'show-rotate-prompt';
5
+ export type TargetViewportSizeClass = 'compact' | 'medium' | 'expanded';
6
+ export type TargetViewportShell = 'browser' | 'mobile-webview' | 'embedded-webview';
7
+ export type TargetViewportMeasurementSource = 'container' | 'visual-viewport' | 'window' | 'unknown';
8
+ export type TargetViewportControlPlacement = 'bottom' | 'side';
9
+ export type TargetViewportPanelPlacement = 'below' | 'side' | 'drawer';
10
+ export interface TargetViewportBreakpoints {
11
+ readonly compactMaxWidth: number;
12
+ readonly expandedMinWidth: number;
13
+ }
14
+ export interface TargetViewportInput {
15
+ readonly width: number;
16
+ readonly height: number;
17
+ readonly runtime?: TargetRuntimeKind;
18
+ readonly source?: TargetViewportMeasurementSource;
19
+ readonly orientationPolicy?: TargetViewportOrientationPolicy;
20
+ }
21
+ export interface TargetViewportLayout {
22
+ readonly width: number;
23
+ readonly height: number;
24
+ readonly shortSide: number;
25
+ readonly longSide: number;
26
+ readonly aspectRatio: number;
27
+ readonly orientation: TargetViewportOrientation;
28
+ readonly sizeClass: TargetViewportSizeClass;
29
+ readonly shell: TargetViewportShell;
30
+ readonly source: TargetViewportMeasurementSource;
31
+ }
32
+ export interface TargetViewportRecommendation {
33
+ readonly primaryControls: TargetViewportControlPlacement;
34
+ readonly secondaryPanels: TargetViewportPanelPlacement;
35
+ readonly safeAreaAware: boolean;
36
+ }
37
+ export interface TargetViewportOrientationPolicy {
38
+ readonly mode: TargetViewportOrientationPolicyMode;
39
+ readonly mismatchBehavior?: TargetViewportOrientationMismatchBehavior;
40
+ }
41
+ export interface TargetViewportOrientationPlan {
42
+ readonly mode: TargetViewportOrientationPolicyMode;
43
+ readonly preferredOrientation?: TargetViewportOrientation;
44
+ readonly lockedOrientation?: TargetViewportOrientation;
45
+ readonly mismatchBehavior: TargetViewportOrientationMismatchBehavior;
46
+ readonly isMismatch: boolean;
47
+ readonly shouldLetterbox: boolean;
48
+ readonly shouldShowRotatePrompt: boolean;
49
+ }
50
+ export interface TargetViewportPlan {
51
+ readonly layout: TargetViewportLayout;
52
+ readonly recommendation: TargetViewportRecommendation;
53
+ readonly orientation: TargetViewportOrientationPlan;
54
+ }
55
+ export declare const defaultTargetViewportBreakpoints: {
56
+ readonly compactMaxWidth: 599;
57
+ readonly expandedMinWidth: 900;
58
+ };
59
+ export declare const defaultTargetViewportOrientationPolicy: {
60
+ readonly mode: 'responsive';
61
+ readonly mismatchBehavior: 'continue';
62
+ };
63
+ export declare function targetViewportShellForRuntime(runtime: TargetRuntimeKind): TargetViewportShell;
64
+ export declare function targetViewportShellForConfig(config: Pick<TargetConfig, 'runtime'>): TargetViewportShell;
65
+ export declare function resolveTargetViewportLayout(input: TargetViewportInput, breakpoints?: TargetViewportBreakpoints): TargetViewportLayout;
66
+ export declare function resolveTargetViewportSizeClass(width: number, breakpoints?: TargetViewportBreakpoints): TargetViewportSizeClass;
67
+ export declare function resolveTargetViewportRecommendation(layout: TargetViewportLayout): TargetViewportRecommendation;
68
+ export declare function resolveTargetViewportOrientationPlan(layout: Pick<TargetViewportLayout, 'orientation'>, policy?: TargetViewportOrientationPolicy): TargetViewportOrientationPlan;
69
+ export declare function resolveTargetViewportPlan(input: TargetViewportInput, breakpoints?: TargetViewportBreakpoints): TargetViewportPlan;
@@ -0,0 +1,156 @@
1
+ export const defaultTargetViewportBreakpoints = {
2
+ compactMaxWidth: 599,
3
+ expandedMinWidth: 900,
4
+ };
5
+ export const defaultTargetViewportOrientationPolicy = {
6
+ mode: 'responsive',
7
+ mismatchBehavior: 'continue',
8
+ };
9
+ const targetViewportOrientationPolicyModeDescriptors = {
10
+ responsive: {
11
+ preferredOrientation: undefined,
12
+ lockedOrientation: undefined,
13
+ isLocked: false,
14
+ },
15
+ 'prefer-landscape': {
16
+ preferredOrientation: 'landscape',
17
+ lockedOrientation: undefined,
18
+ isLocked: false,
19
+ },
20
+ 'prefer-portrait': {
21
+ preferredOrientation: 'portrait',
22
+ lockedOrientation: undefined,
23
+ isLocked: false,
24
+ },
25
+ 'lock-landscape': {
26
+ preferredOrientation: 'landscape',
27
+ lockedOrientation: 'landscape',
28
+ isLocked: true,
29
+ },
30
+ 'lock-portrait': {
31
+ preferredOrientation: 'portrait',
32
+ lockedOrientation: 'portrait',
33
+ isLocked: true,
34
+ },
35
+ };
36
+ export function targetViewportShellForRuntime(runtime) {
37
+ switch (runtime) {
38
+ case 'capacitor-android':
39
+ case 'capacitor-ios':
40
+ case 'apps-in-toss':
41
+ return 'mobile-webview';
42
+ case 'devvit-web':
43
+ return 'embedded-webview';
44
+ case 'web-preview':
45
+ case 'microsoft-store-pwa':
46
+ return 'browser';
47
+ }
48
+ const exhaustive = runtime;
49
+ return exhaustive;
50
+ }
51
+ export function targetViewportShellForConfig(config) {
52
+ return targetViewportShellForRuntime(config.runtime);
53
+ }
54
+ export function resolveTargetViewportLayout(input, breakpoints = defaultTargetViewportBreakpoints) {
55
+ const normalizedBreakpoints = normalizeTargetViewportBreakpoints(breakpoints);
56
+ const width = normalizeViewportDimension(input.width, 'width');
57
+ const height = normalizeViewportDimension(input.height, 'height');
58
+ const aspectRatio = width / height;
59
+ return {
60
+ width,
61
+ height,
62
+ shortSide: Math.min(width, height),
63
+ longSide: Math.max(width, height),
64
+ aspectRatio,
65
+ orientation: width >= height ? 'landscape' : 'portrait',
66
+ sizeClass: classifyTargetViewportSize(width, normalizedBreakpoints),
67
+ shell: input.runtime === undefined ? 'browser' : targetViewportShellForRuntime(input.runtime),
68
+ source: input.source ?? 'unknown',
69
+ };
70
+ }
71
+ export function resolveTargetViewportSizeClass(width, breakpoints = defaultTargetViewportBreakpoints) {
72
+ return classifyTargetViewportSize(normalizeViewportDimension(width, 'width'), normalizeTargetViewportBreakpoints(breakpoints));
73
+ }
74
+ export function resolveTargetViewportRecommendation(layout) {
75
+ const narrowOrPortrait = layout.sizeClass === 'compact' || layout.orientation === 'portrait';
76
+ const browserSafeAreaRisk = layout.shell === 'browser' &&
77
+ layout.orientation === 'landscape' &&
78
+ layout.shortSide <= defaultTargetViewportBreakpoints.compactMaxWidth;
79
+ return {
80
+ primaryControls: narrowOrPortrait ? 'bottom' : 'side',
81
+ secondaryPanels: resolveTargetViewportPanelPlacement(layout),
82
+ safeAreaAware: layout.shell !== 'browser' || layout.sizeClass === 'compact' || browserSafeAreaRisk,
83
+ };
84
+ }
85
+ export function resolveTargetViewportOrientationPlan(layout, policy = defaultTargetViewportOrientationPolicy) {
86
+ const normalizedPolicy = normalizeTargetViewportOrientationPolicy(policy);
87
+ const descriptor = targetViewportOrientationPolicyModeDescriptors[normalizedPolicy.mode];
88
+ const preferredOrientation = descriptor.preferredOrientation;
89
+ const lockedOrientation = descriptor.lockedOrientation;
90
+ const isMismatch = preferredOrientation !== undefined && layout.orientation !== preferredOrientation;
91
+ const mismatchBehavior = normalizedPolicy.mismatchBehavior;
92
+ return {
93
+ mode: normalizedPolicy.mode,
94
+ ...(preferredOrientation === undefined ? {} : { preferredOrientation }),
95
+ ...(lockedOrientation === undefined ? {} : { lockedOrientation }),
96
+ mismatchBehavior,
97
+ isMismatch,
98
+ shouldLetterbox: isMismatch && mismatchBehavior === 'letterbox',
99
+ shouldShowRotatePrompt: isMismatch && mismatchBehavior === 'show-rotate-prompt',
100
+ };
101
+ }
102
+ export function resolveTargetViewportPlan(input, breakpoints = defaultTargetViewportBreakpoints) {
103
+ const layout = resolveTargetViewportLayout(input, breakpoints);
104
+ return {
105
+ layout,
106
+ recommendation: resolveTargetViewportRecommendation(layout),
107
+ orientation: resolveTargetViewportOrientationPlan(layout, input.orientationPolicy),
108
+ };
109
+ }
110
+ function classifyTargetViewportSize(width, breakpoints) {
111
+ if (width <= breakpoints.compactMaxWidth) {
112
+ return 'compact';
113
+ }
114
+ if (width < breakpoints.expandedMinWidth) {
115
+ return 'medium';
116
+ }
117
+ return 'expanded';
118
+ }
119
+ function resolveTargetViewportPanelPlacement(layout) {
120
+ if (layout.sizeClass === 'compact') {
121
+ return 'drawer';
122
+ }
123
+ if (layout.orientation === 'portrait') {
124
+ return 'below';
125
+ }
126
+ return 'side';
127
+ }
128
+ function normalizeTargetViewportOrientationPolicy(policy) {
129
+ const descriptor = targetViewportOrientationPolicyModeDescriptors[policy.mode];
130
+ return {
131
+ mode: policy.mode,
132
+ mismatchBehavior: policy.mismatchBehavior ??
133
+ (descriptor.isLocked ? 'show-rotate-prompt' : 'continue'),
134
+ };
135
+ }
136
+ function normalizeTargetViewportBreakpoints(breakpoints) {
137
+ const compactMaxWidth = normalizeViewportDimension(breakpoints.compactMaxWidth, 'compactMaxWidth');
138
+ const expandedMinWidth = normalizeViewportDimension(breakpoints.expandedMinWidth, 'expandedMinWidth');
139
+ if (compactMaxWidth + 1 >= expandedMinWidth) {
140
+ throw new Error('compactMaxWidth must leave at least one integer width below expandedMinWidth for medium viewports.');
141
+ }
142
+ return {
143
+ compactMaxWidth,
144
+ expandedMinWidth,
145
+ };
146
+ }
147
+ function normalizeViewportDimension(value, name) {
148
+ if (!Number.isFinite(value) || value <= 0) {
149
+ throw new Error(`Viewport ${name} must be a positive finite number.`);
150
+ }
151
+ const rounded = Math.round(value);
152
+ if (rounded <= 0) {
153
+ throw new Error(`Viewport ${name} must round to at least 1.`);
154
+ }
155
+ return rounded;
156
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mpgd/target-config",
3
- "version": "0.3.3",
3
+ "version": "0.4.0",
4
4
  "description": "Target-specific platform feature availability and effective config helpers for mpgd.",
5
5
  "license": "MIT",
6
6
  "repository": {