@motionstudies/web 0.1.0-alpha.13 → 0.1.0-alpha.15
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/README.md
CHANGED
|
@@ -229,3 +229,7 @@ For timetable-backed vehicle selections, `NetworkVehicleHeroCard` from `@motions
|
|
|
229
229
|
### Railway preparation
|
|
230
230
|
|
|
231
231
|
`@motionstudies/data/rail-wtt`, `rail-journeys`, `rail-public-calls`, `rail-routing` and `rail-geometry` expose typed offline railway preparation. XLSX reading, dated source-column audits, UID/date joins, public-call anchors and connected OSM paths preserve source evidence. Editions supply regional identities, calendar exceptions and clipping policy. Node 24+ and `unzip` are required; no Python runtime is shipped. See `docs/SHARED-RAIL-READERS.md` in the source repository for calendar limits and parity evidence.
|
|
232
|
+
|
|
233
|
+
`mountMotionStudy({ id, theme }, application)` requires presentation identity only; a full `MotionStudyEdition` remains accepted. Applications own their data catalogue and loading lifecycle.
|
|
234
|
+
|
|
235
|
+
For independent airport infrastructure, `mapStyle.airports.visible` hides the mounted group without rebuilding GPU resources, and `showLabels` controls its labels separately. Both default to `true`. Set these from the edition's layout and label controls; airport abbreviations equal to their IATA code are rendered once.
|
|
@@ -52,7 +52,9 @@ export interface AirportHeroCardProps {
|
|
|
52
52
|
readonly selectedFlightId?: string;
|
|
53
53
|
readonly initialDirection?: 'departures' | 'arrivals';
|
|
54
54
|
readonly className?: string;
|
|
55
|
+
/** Compact panels retain the same controls and evidence in less vertical space. */
|
|
56
|
+
readonly density?: 'comfortable' | 'compact';
|
|
55
57
|
}
|
|
56
58
|
/** Shared airport selection hero. Supply a key={airport.id} to reset direction on airport changes. */
|
|
57
|
-
export declare function AirportHeroCard({ airport, departures, arrivals, note, study, horizon, maxRows, dateLabel, formatTime, labels, loading, error, onRetry, onSelectFlight, selectedFlightId, initialDirection, className }: AirportHeroCardProps): import("react").JSX.Element;
|
|
59
|
+
export declare function AirportHeroCard({ airport, departures, arrivals, note, study, horizon, maxRows, dateLabel, formatTime, labels, loading, error, onRetry, onSelectFlight, selectedFlightId, initialDirection, className, density }: AirportHeroCardProps): import("react").JSX.Element;
|
|
58
60
|
export {};
|
|
@@ -11,7 +11,7 @@ const defaultLabels = {
|
|
|
11
11
|
studyTime: 'Study time', boardWindow: 'Board window', outsideWindow: 'Outside study window',
|
|
12
12
|
};
|
|
13
13
|
/** Shared airport selection hero. Supply a key={airport.id} to reset direction on airport changes. */
|
|
14
|
-
export function AirportHeroCard({ airport, departures, arrivals, note, study, horizon, maxRows = 8, dateLabel, formatTime = formatServiceTime, labels, loading = false, error, onRetry, onSelectFlight, selectedFlightId, initialDirection = 'departures', className = '' }) {
|
|
14
|
+
export function AirportHeroCard({ airport, departures, arrivals, note, study, horizon, maxRows = 8, dateLabel, formatTime = formatServiceTime, labels, loading = false, error, onRetry, onSelectFlight, selectedFlightId, initialDirection = 'departures', className = '', density }) {
|
|
15
15
|
const [direction, setDirection] = useState(initialDirection);
|
|
16
16
|
const titleId = useId();
|
|
17
17
|
const boardId = useId();
|
|
@@ -20,7 +20,7 @@ export function AirportHeroCard({ airport, departures, arrivals, note, study, ho
|
|
|
20
20
|
const entries = movementsForBoard(direction === 'departures' ? departures : arrivals, window, maxRows);
|
|
21
21
|
const rows = entries.map((entry) => ({ id: entry.id, tone: entry.tone,
|
|
22
22
|
cells: { time: formatTime(entry.time), service: entry.service, place: entry.place, stand: entry.stand, status: entry.status } }));
|
|
23
|
-
return _jsxs("section", { className: `ms-airport-hero ${className}`, "aria-labelledby": titleId, children: [_jsxs("div", { className: "ms-airport-hero__masthead", children: [_jsxs("span", { className: "ms-airport-hero__eyebrow", children: [_jsx("span", { "aria-hidden": "true", children: "\u2197" }), " ", copy.airport, " / ", airport.city] }), dateLabel && _jsx("span", { className: "ms-airport-hero__date", children: dateLabel })] }), _jsxs("div", { className: "ms-airport-hero__identity", children: [_jsx("strong", { className: "ms-airport-hero__code", children: airport.iata }), _jsx("h2", { id: titleId, children: airport.name }), _jsxs("div", { className: "ms-airport-hero__clock", children: [_jsx("span", { className: "ms-airport-hero__clock-label", children: copy.studyTime }), ' ', _jsx("strong", { className: "ms-airport-hero__clock-value", children: Number.isFinite(study.time) ? formatTime(study.time) : '—' })] })] }), _jsxs("div", { className: "ms-airport-hero__board-header", children: [_jsx("div", { className: "ms-airport-hero__directions", role: "group", "aria-label": `${copy.departures} / ${copy.arrivals}`, children: ['departures', 'arrivals'].map((value) => _jsxs("button", { type: "button", "aria-pressed": direction === value, "aria-controls": boardId, onClick: () => setDirection(value), children: [_jsx("span", { "aria-hidden": "true", children: value === 'departures' ? '↗' : '↘' }), " ", copy[value]] }, value)) }), _jsx("p", { className: "ms-airport-hero__window", children: window ? `${copy.boardWindow} ${formatTime(window.start)}–${formatTime(window.end)}` : copy.outsideWindow })] }), _jsx("div", { id: boardId, children: error ? _jsxs("div", { className: "ms-airport-hero__message", role: "status", children: [error, onRetry && _jsx("button", { type: "button", onClick: onRetry, children: copy.retry })] })
|
|
23
|
+
return _jsxs("section", { className: `ms-airport-hero ${className}`, "data-density": density, "aria-labelledby": titleId, children: [_jsxs("div", { className: "ms-airport-hero__masthead", children: [_jsxs("span", { className: "ms-airport-hero__eyebrow", children: [_jsx("span", { "aria-hidden": "true", children: "\u2197" }), " ", copy.airport, " / ", airport.city] }), dateLabel && _jsx("span", { className: "ms-airport-hero__date", children: dateLabel })] }), _jsxs("div", { className: "ms-airport-hero__identity", children: [_jsx("strong", { className: "ms-airport-hero__code", children: airport.iata }), _jsx("h2", { id: titleId, children: airport.name }), _jsxs("div", { className: "ms-airport-hero__clock", children: [_jsx("span", { className: "ms-airport-hero__clock-label", children: copy.studyTime }), ' ', _jsx("strong", { className: "ms-airport-hero__clock-value", children: Number.isFinite(study.time) ? formatTime(study.time) : '—' })] })] }), _jsxs("div", { className: "ms-airport-hero__board-header", children: [_jsx("div", { className: "ms-airport-hero__directions", role: "group", "aria-label": `${copy.departures} / ${copy.arrivals}`, children: ['departures', 'arrivals'].map((value) => _jsxs("button", { type: "button", "aria-pressed": direction === value, "aria-controls": boardId, onClick: () => setDirection(value), children: [_jsx("span", { "aria-hidden": "true", children: value === 'departures' ? '↗' : '↘' }), " ", copy[value]] }, value)) }), _jsx("p", { className: "ms-airport-hero__window", children: window ? `${copy.boardWindow} ${formatTime(window.start)}–${formatTime(window.end)}` : copy.outsideWindow })] }), _jsx("div", { id: boardId, children: error ? _jsxs("div", { className: "ms-airport-hero__message", role: "status", children: [error, onRetry && _jsx("button", { type: "button", onClick: onRetry, children: copy.retry })] })
|
|
24
24
|
: _jsx(SplitFlapBoard, { label: `${airport.iata} ${copy[direction]}`, columns: [
|
|
25
25
|
{ key: 'time', label: copy.time, characters: 5 },
|
|
26
26
|
{ key: 'service', label: copy.service, characters: 7 },
|
|
@@ -40,3 +40,29 @@
|
|
|
40
40
|
.ms-airport-board__modes button:focus-visible, .ms-airport-board__unavailable button:focus-visible { outline: 2px solid #edc96b; outline-offset: 3px; }
|
|
41
41
|
.ms-airport-board__unavailable { padding: 28px; border: 1px solid #4b4d40; border-radius: 12px; color: #f0ecdd; background: #1d211c; font: 14px/1.6 ui-sans-serif, system-ui, sans-serif; }
|
|
42
42
|
.ms-airport-board__detail { color: #b8baaa; font: 13px/1.6 ui-sans-serif, system-ui, sans-serif; overflow-wrap: anywhere; }
|
|
43
|
+
|
|
44
|
+
/* Shared compact presentation, responsive to card width rather than the window. */
|
|
45
|
+
.ms-airport-hero[data-density='compact'] { padding: 12px; }
|
|
46
|
+
.ms-airport-hero[data-density='compact'] .ms-airport-hero__identity { padding: 8px 0; gap: 12px; }
|
|
47
|
+
.ms-airport-hero[data-density='compact'] .ms-airport-hero__code { font-size: 40px; }
|
|
48
|
+
.ms-airport-hero[data-density='compact'] .ms-airport-hero__identity h2 { font-size: 20px; }
|
|
49
|
+
.ms-airport-hero[data-density='compact'] .ms-airport-hero__clock-value { font-size: 28px; }
|
|
50
|
+
.ms-airport-hero[data-density='compact'] .ms-airport-hero__directions { margin-bottom: 8px; }
|
|
51
|
+
.ms-airport-hero[data-density='compact'] .ms-airport-hero__directions button { padding: 6px 0 8px; }
|
|
52
|
+
.ms-airport-hero[data-density='compact'] .ms-airport-hero__window { margin-bottom: 8px; }
|
|
53
|
+
.ms-airport-hero[data-density='compact'] .ms-split-flap-board { padding: 6px 8px 8px; }
|
|
54
|
+
.ms-airport-hero[data-density='compact'] .ms-split-flap-board__table { border-spacing: 0 4px; }
|
|
55
|
+
.ms-airport-hero[data-density='compact'] .ms-split-flap-board__select { min-height: 28px; }
|
|
56
|
+
.ms-airport-hero[data-density='compact'] .ms-flap { font-size: 13px; }
|
|
57
|
+
.ms-airport-hero[data-density='compact'] .ms-airport-hero__note { margin-top: 8px; font-size: 10px; line-height: 1.4; }
|
|
58
|
+
.ms-airport-hero[data-density='compact'] .ms-airport-hero__note a { color: inherit; }
|
|
59
|
+
@container (min-width: 441px) {
|
|
60
|
+
.ms-airport-hero[data-density='compact'] .ms-airport-hero__identity { grid-template-columns: auto minmax(0, 1fr) auto; }
|
|
61
|
+
.ms-airport-hero[data-density='compact'] .ms-airport-hero__clock { grid-column: auto; display: block; padding: 0 0 0 12px; border-top: 0; border-left: 1px solid #797c6155; text-align: right; }
|
|
62
|
+
}
|
|
63
|
+
@container (max-width: 440px) {
|
|
64
|
+
.ms-airport-hero[data-density='compact'] .ms-airport-hero__identity h2 { font-size: 16px; }
|
|
65
|
+
}
|
|
66
|
+
@media (pointer: coarse) {
|
|
67
|
+
.ms-airport-hero[data-density='compact'] .ms-split-flap-board__select { min-height: 44px; }
|
|
68
|
+
}
|
package/mount-motion-study.d.ts
CHANGED
|
@@ -1,3 +1,8 @@
|
|
|
1
1
|
import { type ReactNode } from 'react';
|
|
2
|
-
import type {
|
|
3
|
-
|
|
2
|
+
import type { VisualTheme } from '@motionstudies/core/theme';
|
|
3
|
+
/** Mounting needs presentation identity only; data catalogues belong to the application. */
|
|
4
|
+
export interface MotionStudyMountOptions {
|
|
5
|
+
readonly id: string;
|
|
6
|
+
readonly theme: VisualTheme;
|
|
7
|
+
}
|
|
8
|
+
export declare function mountMotionStudy(edition: MotionStudyMountOptions, application: ReactNode): void;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@motionstudies/web",
|
|
3
|
-
"version": "0.1.0-alpha.
|
|
3
|
+
"version": "0.1.0-alpha.15",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "Browser controls and loaders for Motion Studies.",
|
|
@@ -152,7 +152,8 @@
|
|
|
152
152
|
"types": "./components/NetworkVehicleHeroCard.d.ts",
|
|
153
153
|
"import": "./components/NetworkVehicleHeroCard.js",
|
|
154
154
|
"default": "./components/NetworkVehicleHeroCard.js"
|
|
155
|
-
}
|
|
155
|
+
},
|
|
156
|
+
"./study-layout.css": "./study-layout.css"
|
|
156
157
|
},
|
|
157
158
|
"files": [
|
|
158
159
|
"**/*.js",
|
|
@@ -167,7 +168,7 @@
|
|
|
167
168
|
"**/*.css"
|
|
168
169
|
],
|
|
169
170
|
"dependencies": {
|
|
170
|
-
"@motionstudies/core": "0.1.0-alpha.
|
|
171
|
+
"@motionstudies/core": "0.1.0-alpha.15"
|
|
171
172
|
},
|
|
172
173
|
"peerDependencies": {
|
|
173
174
|
"react": "^19.2.8",
|
package/study-layout.css
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/* Opt-in overlay layout. Clearances describe the edition's surrounding controls. */
|
|
2
|
+
.ms-study-layout {
|
|
3
|
+
--ms-layout-gap: 12px;
|
|
4
|
+
--ms-layout-gutter: clamp(16px, 3vw, 48px);
|
|
5
|
+
--ms-layout-top: 180px;
|
|
6
|
+
--ms-layout-bottom: 160px;
|
|
7
|
+
--ms-layout-end: 72px;
|
|
8
|
+
--ms-layer-panel: 6;
|
|
9
|
+
--ms-control-size: 36px;
|
|
10
|
+
--ms-control-radius: 2px;
|
|
11
|
+
--ms-control-border: var(--line, #c1ccff33);
|
|
12
|
+
--ms-control-surface: var(--panel, #070716ee);
|
|
13
|
+
--ms-control-ink: var(--ink, #f8f7ff);
|
|
14
|
+
--ms-control-accent: var(--cyan, #8dfaff);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
.ms-study-layout .ms-study-panel {
|
|
18
|
+
--ms-panel-top: max(var(--ms-layout-top, 180px), env(safe-area-inset-top));
|
|
19
|
+
--ms-panel-bottom: max(var(--ms-layout-bottom, 160px), env(safe-area-inset-bottom));
|
|
20
|
+
--ms-panel-start: max(var(--ms-layout-gutter, 16px), env(safe-area-inset-left));
|
|
21
|
+
--ms-panel-end: max(var(--ms-layout-end, 72px), env(safe-area-inset-right));
|
|
22
|
+
position: absolute;
|
|
23
|
+
z-index: var(--ms-layer-panel, 6);
|
|
24
|
+
inset-block-start: var(--ms-panel-top);
|
|
25
|
+
inset-inline-start: var(--ms-panel-start);
|
|
26
|
+
box-sizing: border-box;
|
|
27
|
+
inline-size: min(var(--ms-panel-width, 560px), max(0px, calc(100% - var(--ms-panel-start) - var(--ms-panel-end))));
|
|
28
|
+
max-block-size: max(0px, calc(100% - var(--ms-panel-top) - var(--ms-panel-bottom)));
|
|
29
|
+
min-inline-size: 0;
|
|
30
|
+
overflow: auto;
|
|
31
|
+
overscroll-behavior: contain;
|
|
32
|
+
scroll-padding-block: var(--ms-layout-gap, 12px);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
[data-ms-chrome='hidden'] .ms-study-panel {
|
|
36
|
+
visibility: hidden;
|
|
37
|
+
pointer-events: none;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
.ms-control {
|
|
41
|
+
box-sizing: border-box;
|
|
42
|
+
min-inline-size: var(--ms-control-size, 36px);
|
|
43
|
+
min-block-size: var(--ms-control-size, 36px);
|
|
44
|
+
padding: 6px 10px;
|
|
45
|
+
border: 1px solid var(--ms-control-border, #c1ccff33);
|
|
46
|
+
border-radius: var(--ms-control-radius, 2px);
|
|
47
|
+
background: var(--ms-control-surface, #070716ee);
|
|
48
|
+
color: var(--ms-control-ink, #f8f7ff);
|
|
49
|
+
font: inherit;
|
|
50
|
+
cursor: pointer;
|
|
51
|
+
touch-action: manipulation;
|
|
52
|
+
}
|
|
53
|
+
.ms-control:focus-visible {
|
|
54
|
+
outline: 2px solid var(--ms-control-accent, #8dfaff);
|
|
55
|
+
outline-offset: 2px;
|
|
56
|
+
}
|
|
57
|
+
.ms-control[aria-pressed='true'] { border-color: var(--ms-control-accent, #8dfaff); }
|
|
58
|
+
.ms-control:disabled { opacity: .5; cursor: default; }
|
|
59
|
+
.ms-control-group { display: flex; flex-wrap: wrap; align-items: stretch; gap: 6px; }
|
|
60
|
+
.ms-control-strip { display: flex; max-inline-size: 100%; overflow-x: auto; overscroll-behavior-inline: contain; scroll-padding-inline: 4px; }
|
|
61
|
+
.ms-control-strip > * { flex-shrink: 0; }
|
|
62
|
+
.ms-panel-dismiss { position: sticky; inset-block-start: 0; float: inline-end; z-index: 2; margin: 0 0 8px 8px; }
|
|
63
|
+
@media (pointer: coarse) {
|
|
64
|
+
.ms-study-layout { --ms-control-size: 44px; }
|
|
65
|
+
}
|