@sbb-esta/lyne-elements-dev 5.11.0-dev.1791968952 → 5.11.0-dev.1791974141
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/_index.scss +1 -0
- package/core/styles/_theme.scss +4 -0
- package/core/styles/mixins/_pearl-chain-bullet.scss +1 -0
- package/core.js +1 -1
- package/custom-elements.json +1226 -2
- package/development/core.js +3 -0
- package/development/pearl-chain/pearl-chain/pearl-chain.component.d.ts +82 -0
- package/development/pearl-chain/pearl-chain-node/pearl-chain-node.component.d.ts +48 -0
- package/development/pearl-chain.d.ts +1 -0
- package/development/pearl-chain.js +10 -0
- package/development/pearl-chain.pure.d.ts +3 -0
- package/development/pearl-chain.pure.js +605 -0
- package/index.d.ts +3 -0
- package/index.js +3 -0
- package/lean-off-brand-theme.css +22 -0
- package/lean-safety-theme.css +22 -0
- package/lean-theme.css +22 -0
- package/off-brand-theme.css +22 -0
- package/package.json +12 -2
- package/pearl-chain/_pearl-chain.global.scss +26 -0
- package/pearl-chain.js +1 -0
- package/pearl-chain.pure.js +71 -0
- package/safety-theme.css +22 -0
- package/standard-theme.css +22 -0
|
@@ -0,0 +1,605 @@
|
|
|
1
|
+
import { __esDecorate, __runInitializers } from "tslib";
|
|
2
|
+
import { html, nothing, svg, unsafeCSS } from "lit";
|
|
3
|
+
import { property } from "lit/decorators.js";
|
|
4
|
+
import { classMap } from "lit/directives/class-map.js";
|
|
5
|
+
import { ref } from "lit/directives/ref.js";
|
|
6
|
+
import { SbbElement, SbbPropertyWatcherController, defaultDateAdapter, forceType } from "./core.js";
|
|
7
|
+
import { IntersectionController } from "@lit-labs/observers/intersection-controller.js";
|
|
8
|
+
import { ResizeController } from "@lit-labs/observers/resize-controller.js";
|
|
9
|
+
|
|
10
|
+
//#region src/elements/pearl-chain/pearl-chain-node/pearl-chain-node.scss?inline
|
|
11
|
+
var pearl_chain_node_default = ":host {\n display: flex;\n position: relative;\n width: var(--sbb-pearl-chain-node-size);\n height: var(--sbb-pearl-chain-node-size);\n align-items: center;\n justify-content: center;\n}\n\n.bullet {\n width: var(--sbb-pearl-chain-bullet-size);\n height: var(--sbb-pearl-chain-bullet-size);\n color: var(--sbb-pearl-chain-bullet-color);\n rotate: var(--sbb-pearl-chain-bullet-rotation, 0deg);\n}\n.bullet circle {\n cx: 50%;\n cy: 50%;\n}\n\n.bullet--past {\n --sbb-pearl-chain-bullet-color: var(--sbb-pearl-chain-bullet-past-color);\n}\n\n.bullet--disruption {\n --sbb-pearl-chain-bullet-color: var(--sbb-pearl-chain-bullet-disruption-color);\n}\n\n.bullet--irrelevant {\n --sbb-pearl-chain-bullet-color: var(--sbb-pearl-chain-bullet-irrelevant-color);\n --sbb-pearl-chain-bullet-stop-stroke-width: var(--sbb-pearl-chain-irrelevant-stroke-width);\n}\n\n.bullet--stop {\n width: var(--sbb-pearl-chain-bullet-stop-size);\n height: var(--sbb-pearl-chain-bullet-stop-size);\n}\n.bullet--stop circle {\n fill: transparent;\n r: calc(50% - var(--sbb-pearl-chain-bullet-stop-stroke-width) / 2);\n stroke-width: var(--sbb-pearl-chain-bullet-stop-stroke-width);\n}\n\n.bullet--skip .dash {\n fill: var(--sbb-color-error);\n}\n\n.bullet--exceptional-skip {\n width: var(--sbb-pearl-chain-bullet-exceptional-skip-size);\n height: var(--sbb-pearl-chain-bullet-exceptional-skip-size);\n}\n.bullet--exceptional-skip .dash {\n fill: var(--sbb-color-error);\n}\n\n.bullet--stop-duty .outer-circle {\n fill: transparent;\n r: calc(50% - var(--sbb-pearl-chain-bullet-stroke-width) / 2);\n stroke-width: var(--sbb-pearl-chain-bullet-stroke-width);\n stroke-dasharray: 2.1 1;\n}\n\n.bullet--boarding .outer-circle,\n.bullet--alighting .outer-circle,\n.bullet--exceptional-boarding .outer-circle,\n.bullet--exceptional-alighting .outer-circle,\n.bullet--stop-on-demand .outer-circle,\n.bullet--boarding-on-demand .outer-circle,\n.bullet--alighting-on-demand .outer-circle {\n fill: transparent;\n r: calc(50% - var(--sbb-pearl-chain-bullet-stroke-width) / 2);\n stroke-width: var(--sbb-pearl-chain-bullet-stroke-width);\n}\n";
|
|
12
|
+
|
|
13
|
+
//#endregion
|
|
14
|
+
//#region src/elements/pearl-chain/pearl-chain-node/pearl-chain-node.component.ts
|
|
15
|
+
const parseTriState = (value) => value === "arrival" || value === "departure" ? value : value === "" || value === true ? true : null;
|
|
16
|
+
/**
|
|
17
|
+
* Represents a station/point of interest within a `sbb-pearl-chain`.
|
|
18
|
+
*/
|
|
19
|
+
let SbbPearlChainNodeElement = (() => {
|
|
20
|
+
let _classSuper = SbbElement;
|
|
21
|
+
let _instanceExtraInitializers = [];
|
|
22
|
+
let _type_decorators;
|
|
23
|
+
let _type_initializers = [];
|
|
24
|
+
let _type_extraInitializers = [];
|
|
25
|
+
let _disrupted_decorators;
|
|
26
|
+
let _disrupted_initializers = [];
|
|
27
|
+
let _disrupted_extraInitializers = [];
|
|
28
|
+
let _walk_decorators;
|
|
29
|
+
let _walk_initializers = [];
|
|
30
|
+
let _walk_extraInitializers = [];
|
|
31
|
+
let _unsure_decorators;
|
|
32
|
+
let _unsure_initializers = [];
|
|
33
|
+
let _unsure_extraInitializers = [];
|
|
34
|
+
let _irrelevant_decorators;
|
|
35
|
+
let _irrelevant_initializers = [];
|
|
36
|
+
let _irrelevant_extraInitializers = [];
|
|
37
|
+
let _set_arrival_decorators;
|
|
38
|
+
let _set_departure_decorators;
|
|
39
|
+
return class SbbPearlChainNodeElement extends _classSuper {
|
|
40
|
+
static {
|
|
41
|
+
const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
|
|
42
|
+
_type_decorators = [property()];
|
|
43
|
+
_disrupted_decorators = [forceType(parseTriState), property()];
|
|
44
|
+
_walk_decorators = [forceType(parseTriState), property()];
|
|
45
|
+
_unsure_decorators = [forceType(parseTriState), property()];
|
|
46
|
+
_irrelevant_decorators = [forceType(parseTriState), property()];
|
|
47
|
+
_set_arrival_decorators = [property()];
|
|
48
|
+
_set_departure_decorators = [property()];
|
|
49
|
+
__esDecorate(this, null, _type_decorators, {
|
|
50
|
+
kind: "accessor",
|
|
51
|
+
name: "type",
|
|
52
|
+
static: false,
|
|
53
|
+
private: false,
|
|
54
|
+
access: {
|
|
55
|
+
has: (obj) => "type" in obj,
|
|
56
|
+
get: (obj) => obj.type,
|
|
57
|
+
set: (obj, value) => {
|
|
58
|
+
obj.type = value;
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
metadata: _metadata
|
|
62
|
+
}, _type_initializers, _type_extraInitializers);
|
|
63
|
+
__esDecorate(this, null, _disrupted_decorators, {
|
|
64
|
+
kind: "accessor",
|
|
65
|
+
name: "disrupted",
|
|
66
|
+
static: false,
|
|
67
|
+
private: false,
|
|
68
|
+
access: {
|
|
69
|
+
has: (obj) => "disrupted" in obj,
|
|
70
|
+
get: (obj) => obj.disrupted,
|
|
71
|
+
set: (obj, value) => {
|
|
72
|
+
obj.disrupted = value;
|
|
73
|
+
}
|
|
74
|
+
},
|
|
75
|
+
metadata: _metadata
|
|
76
|
+
}, _disrupted_initializers, _disrupted_extraInitializers);
|
|
77
|
+
__esDecorate(this, null, _walk_decorators, {
|
|
78
|
+
kind: "accessor",
|
|
79
|
+
name: "walk",
|
|
80
|
+
static: false,
|
|
81
|
+
private: false,
|
|
82
|
+
access: {
|
|
83
|
+
has: (obj) => "walk" in obj,
|
|
84
|
+
get: (obj) => obj.walk,
|
|
85
|
+
set: (obj, value) => {
|
|
86
|
+
obj.walk = value;
|
|
87
|
+
}
|
|
88
|
+
},
|
|
89
|
+
metadata: _metadata
|
|
90
|
+
}, _walk_initializers, _walk_extraInitializers);
|
|
91
|
+
__esDecorate(this, null, _unsure_decorators, {
|
|
92
|
+
kind: "accessor",
|
|
93
|
+
name: "unsure",
|
|
94
|
+
static: false,
|
|
95
|
+
private: false,
|
|
96
|
+
access: {
|
|
97
|
+
has: (obj) => "unsure" in obj,
|
|
98
|
+
get: (obj) => obj.unsure,
|
|
99
|
+
set: (obj, value) => {
|
|
100
|
+
obj.unsure = value;
|
|
101
|
+
}
|
|
102
|
+
},
|
|
103
|
+
metadata: _metadata
|
|
104
|
+
}, _unsure_initializers, _unsure_extraInitializers);
|
|
105
|
+
__esDecorate(this, null, _irrelevant_decorators, {
|
|
106
|
+
kind: "accessor",
|
|
107
|
+
name: "irrelevant",
|
|
108
|
+
static: false,
|
|
109
|
+
private: false,
|
|
110
|
+
access: {
|
|
111
|
+
has: (obj) => "irrelevant" in obj,
|
|
112
|
+
get: (obj) => obj.irrelevant,
|
|
113
|
+
set: (obj, value) => {
|
|
114
|
+
obj.irrelevant = value;
|
|
115
|
+
}
|
|
116
|
+
},
|
|
117
|
+
metadata: _metadata
|
|
118
|
+
}, _irrelevant_initializers, _irrelevant_extraInitializers);
|
|
119
|
+
__esDecorate(this, null, _set_arrival_decorators, {
|
|
120
|
+
kind: "setter",
|
|
121
|
+
name: "arrival",
|
|
122
|
+
static: false,
|
|
123
|
+
private: false,
|
|
124
|
+
access: {
|
|
125
|
+
has: (obj) => "arrival" in obj,
|
|
126
|
+
set: (obj, value) => {
|
|
127
|
+
obj.arrival = value;
|
|
128
|
+
}
|
|
129
|
+
},
|
|
130
|
+
metadata: _metadata
|
|
131
|
+
}, null, _instanceExtraInitializers);
|
|
132
|
+
__esDecorate(this, null, _set_departure_decorators, {
|
|
133
|
+
kind: "setter",
|
|
134
|
+
name: "departure",
|
|
135
|
+
static: false,
|
|
136
|
+
private: false,
|
|
137
|
+
access: {
|
|
138
|
+
has: (obj) => "departure" in obj,
|
|
139
|
+
set: (obj, value) => {
|
|
140
|
+
obj.departure = value;
|
|
141
|
+
}
|
|
142
|
+
},
|
|
143
|
+
metadata: _metadata
|
|
144
|
+
}, null, _instanceExtraInitializers);
|
|
145
|
+
if (_metadata) Object.defineProperty(this, Symbol.metadata, {
|
|
146
|
+
enumerable: true,
|
|
147
|
+
configurable: true,
|
|
148
|
+
writable: true,
|
|
149
|
+
value: _metadata
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
static {
|
|
153
|
+
this.elementName = "sbb-pearl-chain-node";
|
|
154
|
+
}
|
|
155
|
+
static {
|
|
156
|
+
this.styles = unsafeCSS(pearl_chain_node_default);
|
|
157
|
+
}
|
|
158
|
+
#type_accessor_storage;
|
|
159
|
+
/** Decides how the bullet is rendered. Defaults to _empty_ (no bullet). */
|
|
160
|
+
get type() {
|
|
161
|
+
return this.#type_accessor_storage;
|
|
162
|
+
}
|
|
163
|
+
set type(value) {
|
|
164
|
+
this.#type_accessor_storage = value;
|
|
165
|
+
}
|
|
166
|
+
#disrupted_accessor_storage;
|
|
167
|
+
/** Marks the node as disrupted, coloring the bullet red (if rendered). */
|
|
168
|
+
get disrupted() {
|
|
169
|
+
return this.#disrupted_accessor_storage;
|
|
170
|
+
}
|
|
171
|
+
set disrupted(value) {
|
|
172
|
+
this.#disrupted_accessor_storage = value;
|
|
173
|
+
}
|
|
174
|
+
#walk_accessor_storage;
|
|
175
|
+
/** Marks the node as a walk connection. Does not affect the bullet color. */
|
|
176
|
+
get walk() {
|
|
177
|
+
return this.#walk_accessor_storage;
|
|
178
|
+
}
|
|
179
|
+
set walk(value) {
|
|
180
|
+
this.#walk_accessor_storage = value;
|
|
181
|
+
}
|
|
182
|
+
#unsure_accessor_storage;
|
|
183
|
+
/** Marks the connection as unsure, rendering the line dashed. Does not affect the bullet. */
|
|
184
|
+
get unsure() {
|
|
185
|
+
return this.#unsure_accessor_storage;
|
|
186
|
+
}
|
|
187
|
+
set unsure(value) {
|
|
188
|
+
this.#unsure_accessor_storage = value;
|
|
189
|
+
}
|
|
190
|
+
#irrelevant_accessor_storage;
|
|
191
|
+
/** Marks the node as irrelevant, coloring the bullet gray (if rendered); supersedes `disrupted`. */
|
|
192
|
+
get irrelevant() {
|
|
193
|
+
return this.#irrelevant_accessor_storage;
|
|
194
|
+
}
|
|
195
|
+
set irrelevant(value) {
|
|
196
|
+
this.#irrelevant_accessor_storage = value;
|
|
197
|
+
}
|
|
198
|
+
/** The arrival date/time at this node. Accepts ISO 8601 datetime strings. */
|
|
199
|
+
set arrival(value) {
|
|
200
|
+
this._arrival = defaultDateAdapter.getValidDateOrNull(defaultDateAdapter.deserialize(value));
|
|
201
|
+
}
|
|
202
|
+
get arrival() {
|
|
203
|
+
return this._arrival;
|
|
204
|
+
}
|
|
205
|
+
/** The departure date/time at this node. Accepts ISO 8601 datetime strings. */
|
|
206
|
+
set departure(value) {
|
|
207
|
+
this._departure = defaultDateAdapter.getValidDateOrNull(defaultDateAdapter.deserialize(value));
|
|
208
|
+
}
|
|
209
|
+
get departure() {
|
|
210
|
+
return this._departure;
|
|
211
|
+
}
|
|
212
|
+
get now() {
|
|
213
|
+
return this._chain?.now ?? this._now;
|
|
214
|
+
}
|
|
215
|
+
constructor() {
|
|
216
|
+
super();
|
|
217
|
+
this.#type_accessor_storage = (__runInitializers(this, _instanceExtraInitializers), __runInitializers(this, _type_initializers, null));
|
|
218
|
+
this.#disrupted_accessor_storage = (__runInitializers(this, _type_extraInitializers), __runInitializers(this, _disrupted_initializers, null));
|
|
219
|
+
this.#walk_accessor_storage = (__runInitializers(this, _disrupted_extraInitializers), __runInitializers(this, _walk_initializers, null));
|
|
220
|
+
this.#unsure_accessor_storage = (__runInitializers(this, _walk_extraInitializers), __runInitializers(this, _unsure_initializers, null));
|
|
221
|
+
this.#irrelevant_accessor_storage = (__runInitializers(this, _unsure_extraInitializers), __runInitializers(this, _irrelevant_initializers, null));
|
|
222
|
+
this._arrival = (__runInitializers(this, _irrelevant_extraInitializers), null);
|
|
223
|
+
this._departure = null;
|
|
224
|
+
this._now = /* @__PURE__ */ new Date();
|
|
225
|
+
this._chain = null;
|
|
226
|
+
this.addController(new SbbPropertyWatcherController(this, () => this.closest("sbb-pearl-chain"), { now: () => this.requestUpdate() }));
|
|
227
|
+
}
|
|
228
|
+
connectedCallback() {
|
|
229
|
+
super.connectedCallback();
|
|
230
|
+
this._chain = this.closest("sbb-pearl-chain");
|
|
231
|
+
this._chain?.addNode(this);
|
|
232
|
+
}
|
|
233
|
+
disconnectedCallback() {
|
|
234
|
+
super.disconnectedCallback();
|
|
235
|
+
this._chain?.removeNode(this);
|
|
236
|
+
this._chain = null;
|
|
237
|
+
}
|
|
238
|
+
updated(changedProperties) {
|
|
239
|
+
super.updated(changedProperties);
|
|
240
|
+
if (changedProperties.size > 0) this._chain?.requestUpdate();
|
|
241
|
+
}
|
|
242
|
+
_isPast() {
|
|
243
|
+
return !!this.departure && this.departure.getTime() < this.now.getTime();
|
|
244
|
+
}
|
|
245
|
+
_renderBullet() {
|
|
246
|
+
switch (this.type) {
|
|
247
|
+
case "start":
|
|
248
|
+
case "end":
|
|
249
|
+
case "commercial":
|
|
250
|
+
case "exceptional": return svg`
|
|
251
|
+
<circle r="50%" fill="currentColor" />
|
|
252
|
+
`;
|
|
253
|
+
case "stop": return svg`
|
|
254
|
+
<circle
|
|
255
|
+
stroke="currentColor"
|
|
256
|
+
/>
|
|
257
|
+
`;
|
|
258
|
+
case "skip": return svg`
|
|
259
|
+
<path d="m3.65 2.59c-.36.36-.59.86-.59 1.41 0 1.1.9 2 2 2 .55 0 1.05-.22 1.41-.59l1.42 1.42c-.73.72-1.72 1.17-2.83 1.17-2.21 0-4-1.79-4-4 0-1.1.45-2.1 1.17-2.83l1.42 1.42zm1.41-2.59c2.21 0 4 1.79 4 4 0 .55-.11 1.08-.32 1.56l-1.69-1.69c-.06-1-.86-1.8-1.87-1.87l-1.68-1.68c.48-.21 1.01-.32 1.56-.32z" fill="currentColor"/>
|
|
260
|
+
<path class="dash" d="m1.06 0 7.78 7.78-1.06 1.06-7.78-7.78 1.06-1.06z"/>
|
|
261
|
+
`;
|
|
262
|
+
case "exceptional-skip": return svg`
|
|
263
|
+
<path d="m9.5 8.5c-.9.9-2.1 1.5-3.5 1.5-2.7 0-5-2.2-5-5 0-1.4.6-2.6 1.5-3.5l7 7zm-3.5-8.5c2.8 0 5 2.2 5 5 0 .8-.2 1.6-.6 2.3l-6.7-6.7c.7-.4 1.5-.6 2.3-.6z" fill="currentColor"/>
|
|
264
|
+
<rect class="dash" x="1.41431" width="14" height="2" transform="rotate(45 1.41431 0)"/>
|
|
265
|
+
`;
|
|
266
|
+
case "stop-duty": return svg`
|
|
267
|
+
<circle
|
|
268
|
+
class="outer-circle"
|
|
269
|
+
stroke="currentColor"
|
|
270
|
+
/>
|
|
271
|
+
`;
|
|
272
|
+
case "boarding":
|
|
273
|
+
case "exceptional-boarding": return svg`
|
|
274
|
+
<circle
|
|
275
|
+
class="outer-circle"
|
|
276
|
+
stroke="currentColor"
|
|
277
|
+
/>
|
|
278
|
+
<path d="m1.85 5c0 .84.34 1.64.92 2.23.59.58 1.39.92 2.23.92.84 0 1.64-.34 2.23-.92.58-.59.92-1.39.92-2.23l-3.15 0-3.15 0z" fill="currentColor"/>
|
|
279
|
+
`;
|
|
280
|
+
case "alighting":
|
|
281
|
+
case "exceptional-alighting": return svg`
|
|
282
|
+
<circle
|
|
283
|
+
class="outer-circle"
|
|
284
|
+
stroke="currentColor"
|
|
285
|
+
/>
|
|
286
|
+
<path d="m8.15 5c0-.84-.34-1.64-.92-2.23-.59-.58-1.39-.92-2.23-.92-.84 0-1.64.34-2.23.92-.58.59-.92 1.39-.92 2.23l3.15 0 3.15 0z" fill="currentColor"/>
|
|
287
|
+
`;
|
|
288
|
+
case "stop-on-demand": return svg`
|
|
289
|
+
<circle
|
|
290
|
+
class="outer-circle"
|
|
291
|
+
stroke="currentColor"
|
|
292
|
+
/>
|
|
293
|
+
<circle
|
|
294
|
+
fill="currentColor"
|
|
295
|
+
r="20%"
|
|
296
|
+
/>
|
|
297
|
+
`;
|
|
298
|
+
case "boarding-on-demand": return svg`
|
|
299
|
+
<circle
|
|
300
|
+
class="outer-circle"
|
|
301
|
+
stroke="currentColor"
|
|
302
|
+
/>
|
|
303
|
+
<path d="m3 5c0 .53.21 1.04.59 1.41.37.38.88.59 1.41.59.53 0 1.04-.21 1.41-.59.38-.37.59-.88.59-1.41l-2 0h-2z" fill="currentColor"/>
|
|
304
|
+
`;
|
|
305
|
+
case "alighting-on-demand": return svg`
|
|
306
|
+
<circle
|
|
307
|
+
class="outer-circle"
|
|
308
|
+
stroke="currentColor"
|
|
309
|
+
/>
|
|
310
|
+
<path d="m7 5c0-.53-.21-1.04-.59-1.41-.37-.38-.88-.59-1.41-.59-.53 0-1.04.21-1.41.59-.38.37-.59.88-.59 1.41l2 0h2z" fill="currentColor"/>
|
|
311
|
+
`;
|
|
312
|
+
default: return nothing;
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
render() {
|
|
316
|
+
return html`
|
|
317
|
+
<svg
|
|
318
|
+
viewBox=${this.type === "exceptional-skip" ? "0 0 12 12" : "0 0 10 10"}
|
|
319
|
+
class="bullet ${classMap({
|
|
320
|
+
[`bullet--${this.type}`]: !!this.type,
|
|
321
|
+
"bullet--irrelevant": !!this.irrelevant,
|
|
322
|
+
"bullet--disruption": !!this.disrupted,
|
|
323
|
+
"bullet--past": this._isPast()
|
|
324
|
+
})}"
|
|
325
|
+
aria-hidden="true"
|
|
326
|
+
${ref((el) => {
|
|
327
|
+
this.svgElem = el;
|
|
328
|
+
})}
|
|
329
|
+
>
|
|
330
|
+
${this._renderBullet()}
|
|
331
|
+
</svg>
|
|
332
|
+
`;
|
|
333
|
+
}
|
|
334
|
+
};
|
|
335
|
+
})();
|
|
336
|
+
|
|
337
|
+
//#endregion
|
|
338
|
+
//#region src/elements/pearl-chain/pearl-chain/pearl-chain.scss?inline
|
|
339
|
+
var pearl_chain_default = ":host {\n position: relative;\n display: block;\n --sbb-pearl-chain-bullet-now-animation-duration: var(--sbb-disable-animation-duration, 2s);\n}\n\n:host(:is(:state(horizontal),[state--horizontal])) {\n --sbb-pearl-chain-bullet-rotation: -90deg;\n}\n\n.sbb-pearl-chain__svg {\n position: absolute;\n inset: 0;\n width: 100%;\n height: 100%;\n overflow: visible;\n pointer-events: none;\n}\n\n.line {\n stroke: var(--sbb-pearl-chain-color);\n stroke-width: var(--sbb-pearl-chain-stroke-width);\n}\n\n.line--past {\n stroke: var(--sbb-pearl-chain-past-color);\n}\n\n.line--walk {\n stroke: var(--sbb-pearl-chain-walk-color);\n stroke-dasharray: 0.125rem;\n}\n\n.line--unsure {\n stroke-dasharray: 0.125rem;\n}\n\n.line--disruption {\n stroke: var(--sbb-pearl-chain-disruption-color);\n stroke-dasharray: 0.125rem;\n}\n\n.line--irrelevant {\n stroke: var(--sbb-pearl-chain-irrelevant-color);\n stroke-width: var(--sbb-pearl-chain-irrelevant-stroke-width);\n}\n\n.now-dot {\n fill: var(--sbb-pearl-chain-now-color);\n stroke: var(--sbb-pearl-chain-now-color);\n stroke-opacity: var(--sbb-pearl-chain-bullet-now-animation-opacity);\n r: calc(var(--sbb-pearl-chain-now-size) / 2);\n animation: sbb-pearl-chain-pulse var(--sbb-pearl-chain-bullet-now-animation-duration) ease-out infinite;\n}\n\n@keyframes sbb-pearl-chain-pulse {\n 0% {\n stroke-width: 0;\n }\n 50% {\n stroke-width: calc(var(--sbb-pearl-chain-now-size) / 2);\n }\n 100% {\n stroke-width: 0;\n }\n}\n";
|
|
340
|
+
|
|
341
|
+
//#endregion
|
|
342
|
+
//#region src/elements/pearl-chain/pearl-chain/pearl-chain.component.ts
|
|
343
|
+
/** Gap (in px) left before a node's bullet, so the line doesn't touch it. */
|
|
344
|
+
const NODE_GAP = 2;
|
|
345
|
+
/** Interval (in ms) at which the component is re-rendered. */
|
|
346
|
+
const RENDERING_INTERVAL = 3e4;
|
|
347
|
+
const DEBOUNCE_TIME = 150;
|
|
348
|
+
/**
|
|
349
|
+
* Linearly interpolates between two points.
|
|
350
|
+
*/
|
|
351
|
+
const lerp = (from, to, ratio) => ({
|
|
352
|
+
x: from.x + (to.x - from.x) * ratio,
|
|
353
|
+
y: from.y + (to.y - from.y) * ratio
|
|
354
|
+
});
|
|
355
|
+
/** Pulls `to` back towards `from` by `gap` px, along the line's direction. */
|
|
356
|
+
const pullBack = (from, to, gap) => {
|
|
357
|
+
const length = Math.hypot(to.x - from.x, to.y - from.y);
|
|
358
|
+
return length === 0 ? to : lerp(from, to, Math.max(0, (length - gap) / length));
|
|
359
|
+
};
|
|
360
|
+
/**
|
|
361
|
+
* It displays a chain of `sbb-pearl-chain-node` elements connected by lines,
|
|
362
|
+
* representing a journey with its stops.
|
|
363
|
+
*/
|
|
364
|
+
let SbbPearlChainElement = (() => {
|
|
365
|
+
let _classSuper = SbbElement;
|
|
366
|
+
let _instanceExtraInitializers = [];
|
|
367
|
+
let _set_now_decorators;
|
|
368
|
+
return class SbbPearlChainElement extends _classSuper {
|
|
369
|
+
static {
|
|
370
|
+
const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
|
|
371
|
+
_set_now_decorators = [property()];
|
|
372
|
+
__esDecorate(this, null, _set_now_decorators, {
|
|
373
|
+
kind: "setter",
|
|
374
|
+
name: "now",
|
|
375
|
+
static: false,
|
|
376
|
+
private: false,
|
|
377
|
+
access: {
|
|
378
|
+
has: (obj) => "now" in obj,
|
|
379
|
+
set: (obj, value) => {
|
|
380
|
+
obj.now = value;
|
|
381
|
+
}
|
|
382
|
+
},
|
|
383
|
+
metadata: _metadata
|
|
384
|
+
}, null, _instanceExtraInitializers);
|
|
385
|
+
if (_metadata) Object.defineProperty(this, Symbol.metadata, {
|
|
386
|
+
enumerable: true,
|
|
387
|
+
configurable: true,
|
|
388
|
+
writable: true,
|
|
389
|
+
value: _metadata
|
|
390
|
+
});
|
|
391
|
+
}
|
|
392
|
+
static {
|
|
393
|
+
this.elementName = "sbb-pearl-chain";
|
|
394
|
+
}
|
|
395
|
+
static {
|
|
396
|
+
this.styles = unsafeCSS(pearl_chain_default);
|
|
397
|
+
}
|
|
398
|
+
/**
|
|
399
|
+
* A configured date which acts as the current date instead of the real current date.
|
|
400
|
+
* Only recommended for testing purposes.
|
|
401
|
+
*/
|
|
402
|
+
set now(value) {
|
|
403
|
+
this._userNow = defaultDateAdapter.getValidDateOrNull(defaultDateAdapter.deserialize(value));
|
|
404
|
+
}
|
|
405
|
+
get now() {
|
|
406
|
+
return this._userNow ?? this._systemNow;
|
|
407
|
+
}
|
|
408
|
+
/** Returns the registered `sbb-pearl-chain-node` elements, in DOM order. */
|
|
409
|
+
get nodes() {
|
|
410
|
+
return this._nodes.slice();
|
|
411
|
+
}
|
|
412
|
+
constructor() {
|
|
413
|
+
super();
|
|
414
|
+
this._userNow = (__runInitializers(this, _instanceExtraInitializers), null);
|
|
415
|
+
this._systemNow = /* @__PURE__ */ new Date();
|
|
416
|
+
this._nodes = [];
|
|
417
|
+
this._orientation = "vertical";
|
|
418
|
+
const controllersConfig = {
|
|
419
|
+
callback: () => this._debouncedPositionLines(),
|
|
420
|
+
skipInitial: true
|
|
421
|
+
};
|
|
422
|
+
this.addController(new ResizeController(this, controllersConfig));
|
|
423
|
+
this.addController(new IntersectionController(this, controllersConfig));
|
|
424
|
+
}
|
|
425
|
+
/**
|
|
426
|
+
* Returns the relative position (0 for the first node, 1 for the last one) of each node,
|
|
427
|
+
* in DOM order, proportional to its `departure` time (or `arrival` if there is no `departure`).
|
|
428
|
+
*/
|
|
429
|
+
calculateNodeWeights() {
|
|
430
|
+
const times = this._nodes.map((n) => (n.departure ?? n.arrival).getTime());
|
|
431
|
+
const first = times[0];
|
|
432
|
+
const range = times[times.length - 1] - first;
|
|
433
|
+
return times.map((t) => (t - first) / range);
|
|
434
|
+
}
|
|
435
|
+
/**
|
|
436
|
+
* Registers a `sbb-pearl-chain-node` with this pearl chain.
|
|
437
|
+
* @internal
|
|
438
|
+
*/
|
|
439
|
+
addNode(node) {
|
|
440
|
+
if (this._nodes.includes(node)) return;
|
|
441
|
+
this._nodes.push(node);
|
|
442
|
+
this._nodes.sort((a, b) => a.compareDocumentPosition(b) & Node.DOCUMENT_POSITION_FOLLOWING ? -1 : 1);
|
|
443
|
+
this.requestUpdate();
|
|
444
|
+
}
|
|
445
|
+
/**
|
|
446
|
+
* Unregisters a `sbb-pearl-chain-node` from this pearl chain.
|
|
447
|
+
* @internal
|
|
448
|
+
*/
|
|
449
|
+
removeNode(node) {
|
|
450
|
+
const index = this._nodes.indexOf(node);
|
|
451
|
+
if (index === -1) return;
|
|
452
|
+
this._nodes.splice(index, 1);
|
|
453
|
+
this.requestUpdate();
|
|
454
|
+
}
|
|
455
|
+
connectedCallback() {
|
|
456
|
+
super.connectedCallback();
|
|
457
|
+
this._systemNowIntervalId = setInterval(() => {
|
|
458
|
+
this._systemNow = /* @__PURE__ */ new Date();
|
|
459
|
+
this.requestUpdate();
|
|
460
|
+
}, RENDERING_INTERVAL);
|
|
461
|
+
}
|
|
462
|
+
disconnectedCallback() {
|
|
463
|
+
super.disconnectedCallback();
|
|
464
|
+
clearInterval(this._systemNowIntervalId);
|
|
465
|
+
}
|
|
466
|
+
updated(changedProperties) {
|
|
467
|
+
super.updated(changedProperties);
|
|
468
|
+
this._positionLines();
|
|
469
|
+
}
|
|
470
|
+
firstUpdated(changedProperties) {
|
|
471
|
+
super.firstUpdated(changedProperties);
|
|
472
|
+
this._svgElem = this.shadowRoot.querySelector(".sbb-pearl-chain__svg");
|
|
473
|
+
}
|
|
474
|
+
/**
|
|
475
|
+
* Optimizes `_positionLines()` calls by debouncing them (called by observers).
|
|
476
|
+
*/
|
|
477
|
+
_debouncedPositionLines() {
|
|
478
|
+
clearTimeout(this._debounceId);
|
|
479
|
+
this._debounceId = setTimeout(() => this._positionLines(), DEBOUNCE_TIME);
|
|
480
|
+
}
|
|
481
|
+
/**
|
|
482
|
+
* Positions the rendered lines (and dot) to connect the bullet nodes, and toggles
|
|
483
|
+
* `:is(:state(horizontal),[state--horizontal])` based on whether nodes are spread more horizontally than vertically.
|
|
484
|
+
*/
|
|
485
|
+
_positionLines() {
|
|
486
|
+
const elements = this.shadowRoot?.querySelectorAll("[data-segment]");
|
|
487
|
+
if (!elements?.length || !this._svgElem) return;
|
|
488
|
+
const svgRect = this._svgElem.getBoundingClientRect();
|
|
489
|
+
const nodesCords = this._nodes.map((node) => {
|
|
490
|
+
const rect = node["svgElem"]?.getBoundingClientRect() ?? node.getBoundingClientRect();
|
|
491
|
+
return {
|
|
492
|
+
x: rect.left + rect.width / 2 - svgRect.left,
|
|
493
|
+
y: rect.top + rect.height / 2 - svgRect.top,
|
|
494
|
+
size: rect.width
|
|
495
|
+
};
|
|
496
|
+
});
|
|
497
|
+
this._updateOrientation(nodesCords);
|
|
498
|
+
for (const el of elements) {
|
|
499
|
+
const index = Number(el.dataset.segment);
|
|
500
|
+
const start = pullBack(nodesCords[index + 1], nodesCords[index], nodesCords[index].size / 2);
|
|
501
|
+
const end = this._needsGap(this._nodes[index + 1]) ? pullBack(start, nodesCords[index + 1], NODE_GAP + nodesCords[index + 1].size / 2) : pullBack(start, nodesCords[index + 1], nodesCords[index + 1].size / 2);
|
|
502
|
+
switch (el.dataset.role) {
|
|
503
|
+
case "dot": {
|
|
504
|
+
const ratio = this._progressRatio(this._nodes[index], this._nodes[index + 1]);
|
|
505
|
+
const nowCords = lerp(nodesCords[index], nodesCords[index + 1], ratio);
|
|
506
|
+
el.setAttribute("cx", `${nowCords.x}`);
|
|
507
|
+
el.setAttribute("cy", `${nowCords.y}`);
|
|
508
|
+
break;
|
|
509
|
+
}
|
|
510
|
+
case "before": {
|
|
511
|
+
const ratio = this._progressRatio(this._nodes[index], this._nodes[index + 1]);
|
|
512
|
+
const nowCords = lerp(start, end, ratio);
|
|
513
|
+
this._setLineCords(el, start, nowCords);
|
|
514
|
+
break;
|
|
515
|
+
}
|
|
516
|
+
case "after": {
|
|
517
|
+
const ratio = this._progressRatio(this._nodes[index], this._nodes[index + 1]);
|
|
518
|
+
const nowCords = lerp(start, end, ratio);
|
|
519
|
+
this._setLineCords(el, nowCords, end);
|
|
520
|
+
break;
|
|
521
|
+
}
|
|
522
|
+
default: this._setLineCords(el, start, end);
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
/** Sets the coordinates of a line svg element. */
|
|
527
|
+
_setLineCords(element, start, end) {
|
|
528
|
+
element.setAttribute("x1", `${start.x}`);
|
|
529
|
+
element.setAttribute("y1", `${start.y}`);
|
|
530
|
+
element.setAttribute("x2", `${end.x}`);
|
|
531
|
+
element.setAttribute("y2", `${end.y}`);
|
|
532
|
+
}
|
|
533
|
+
/** Toggles `:is(:state(horizontal),[state--horizontal])` when the nodes are spread more horizontally than vertically. */
|
|
534
|
+
_updateOrientation(nodesCords) {
|
|
535
|
+
let minX = Infinity, maxX = -Infinity, minY = Infinity, maxY = -Infinity;
|
|
536
|
+
for (const { x, y } of nodesCords) {
|
|
537
|
+
if (x < minX) minX = x;
|
|
538
|
+
if (x > maxX) maxX = x;
|
|
539
|
+
if (y < minY) minY = y;
|
|
540
|
+
if (y > maxY) maxY = y;
|
|
541
|
+
}
|
|
542
|
+
const horizontal = maxX - minX > maxY - minY;
|
|
543
|
+
this.toggleState("horizontal", horizontal);
|
|
544
|
+
this._orientation = horizontal ? "horizontal" : "vertical";
|
|
545
|
+
}
|
|
546
|
+
/** No gap for `start` or `end` bullets. */
|
|
547
|
+
_needsGap(node) {
|
|
548
|
+
return this._orientation === "horizontal" && node.type !== null && node.type !== "start" && node.type !== "end";
|
|
549
|
+
}
|
|
550
|
+
/**
|
|
551
|
+
* Ratio (0-1) of "now" between the line's start departure and end arrival, or
|
|
552
|
+
* `null` if "now" is outside that window (or either date is missing).
|
|
553
|
+
*/
|
|
554
|
+
_progressRatio(start, end) {
|
|
555
|
+
const from = start.departure;
|
|
556
|
+
const to = end.arrival;
|
|
557
|
+
if (!from || !to) return null;
|
|
558
|
+
const now = this.now.getTime();
|
|
559
|
+
if (now < from.getTime() || now > to.getTime()) return null;
|
|
560
|
+
return (now - from.getTime()) / (to.getTime() - from.getTime());
|
|
561
|
+
}
|
|
562
|
+
/**
|
|
563
|
+
* Returns a map of CSS classes for a line segment between two nodes, based on its state.
|
|
564
|
+
*/
|
|
565
|
+
_lineClasses(start, end) {
|
|
566
|
+
return {
|
|
567
|
+
"line--past": this._isLinePast(end),
|
|
568
|
+
"line--walk": start.walk === true || start.walk === "departure" || end.walk === true || end.walk === "arrival",
|
|
569
|
+
"line--unsure": start.unsure === true || start.unsure === "departure" || end.unsure === true || end.unsure === "arrival",
|
|
570
|
+
"line--irrelevant": start.irrelevant === true || start.irrelevant === "departure" || end.irrelevant === true || end.irrelevant === "arrival",
|
|
571
|
+
"line--disruption": start.disrupted === true || start.disrupted === "departure" || end.disrupted === true || end.disrupted === "arrival"
|
|
572
|
+
};
|
|
573
|
+
}
|
|
574
|
+
/** A line is "past" once we've already arrived at its ending node. */
|
|
575
|
+
_isLinePast(end) {
|
|
576
|
+
const pastTime = end.arrival ?? end.departure;
|
|
577
|
+
return !!pastTime && pastTime.getTime() < this.now.getTime();
|
|
578
|
+
}
|
|
579
|
+
/** A line is "current" if "now" falls between its start departure and end arrival. */
|
|
580
|
+
_isLineCurrent(start, end) {
|
|
581
|
+
return this._progressRatio(start, end) !== null;
|
|
582
|
+
}
|
|
583
|
+
/** Renders one segment: a single line, or (when "now" falls within it) a gray/normal split + pulsing dot. */
|
|
584
|
+
_renderSegment(start, end, index) {
|
|
585
|
+
const classes = this._lineClasses(start, end);
|
|
586
|
+
if (!classes["line--disruption"] && !classes["line--walk"] && this._isLineCurrent(start, end)) return svg`
|
|
587
|
+
<line class="line line--past" data-segment="${index}" data-role="before"></line>
|
|
588
|
+
<line class="line ${classMap(classes)}" data-segment="${index}" data-role="after"></line>
|
|
589
|
+
<circle class="now-dot" data-segment="${index}" data-role="dot"></circle>
|
|
590
|
+
`;
|
|
591
|
+
return svg`<line class="line ${classMap(classes)}" data-segment="${index}"></line>`;
|
|
592
|
+
}
|
|
593
|
+
render() {
|
|
594
|
+
return html`
|
|
595
|
+
<slot></slot>
|
|
596
|
+
<svg class="sbb-pearl-chain__svg" aria-hidden="true">
|
|
597
|
+
${this._nodes.length > 1 ? Array.from({ length: this._nodes.length - 1 }, (_, index) => this._renderSegment(this._nodes[index], this._nodes[index + 1], index)) : nothing}
|
|
598
|
+
</svg>
|
|
599
|
+
`;
|
|
600
|
+
}
|
|
601
|
+
};
|
|
602
|
+
})();
|
|
603
|
+
|
|
604
|
+
//#endregion
|
|
605
|
+
export { SbbPearlChainElement, SbbPearlChainNodeElement };
|
package/index.d.ts
CHANGED
|
@@ -45,6 +45,7 @@ import { SbbNotificationElement } from "./notification.js";
|
|
|
45
45
|
import { SbbOptGroupElement, SbbOptionElement, SbbOptionHintElement } from "./option.js";
|
|
46
46
|
import { SbbOverlayElement } from "./overlay.js";
|
|
47
47
|
import { SbbCompactPaginatorElement, SbbPaginatorElement } from "./paginator.js";
|
|
48
|
+
import { SbbPearlChainElement, SbbPearlChainNodeElement } from "./pearl-chain.js";
|
|
48
49
|
import { SbbPopoverCloseButtonElement, SbbPopoverElement } from "./popover.js";
|
|
49
50
|
import { SbbRadioButtonElement } from "./radio-button.js";
|
|
50
51
|
import { SbbRadioButtonGroupElement } from "./radio-button-group.js";
|
|
@@ -192,6 +193,8 @@ declare global {
|
|
|
192
193
|
var SbbOptionHintElement: SbbOptionHintElement;
|
|
193
194
|
var SbbOverlayElement: SbbOverlayElement;
|
|
194
195
|
var SbbPaginatorElement: SbbPaginatorElement;
|
|
196
|
+
var SbbPearlChainElement: SbbPearlChainElement;
|
|
197
|
+
var SbbPearlChainNodeElement: SbbPearlChainNodeElement;
|
|
195
198
|
var SbbPopoverCloseButtonElement: SbbPopoverCloseButtonElement;
|
|
196
199
|
var SbbPopoverElement: SbbPopoverElement;
|
|
197
200
|
var SbbRadioButtonElement: SbbRadioButtonElement;
|
package/index.js
CHANGED
|
@@ -45,6 +45,7 @@ import { SbbNotificationElement } from "./notification.js";
|
|
|
45
45
|
import { SbbOptGroupElement, SbbOptionElement, SbbOptionHintElement } from "./option.js";
|
|
46
46
|
import { SbbOverlayElement } from "./overlay.js";
|
|
47
47
|
import { SbbCompactPaginatorElement, SbbPaginatorElement } from "./paginator.js";
|
|
48
|
+
import { SbbPearlChainElement, SbbPearlChainNodeElement } from "./pearl-chain.js";
|
|
48
49
|
import { SbbPopoverCloseButtonElement, SbbPopoverElement } from "./popover.js";
|
|
49
50
|
import { SbbRadioButtonElement } from "./radio-button.js";
|
|
50
51
|
import { SbbRadioButtonGroupElement } from "./radio-button-group.js";
|
|
@@ -192,6 +193,8 @@ globalThis.SbbOptionElement = SbbOptionElement;
|
|
|
192
193
|
globalThis.SbbOptionHintElement = SbbOptionHintElement;
|
|
193
194
|
globalThis.SbbOverlayElement = SbbOverlayElement;
|
|
194
195
|
globalThis.SbbPaginatorElement = SbbPaginatorElement;
|
|
196
|
+
globalThis.SbbPearlChainElement = SbbPearlChainElement;
|
|
197
|
+
globalThis.SbbPearlChainNodeElement = SbbPearlChainNodeElement;
|
|
195
198
|
globalThis.SbbPopoverCloseButtonElement = SbbPopoverCloseButtonElement;
|
|
196
199
|
globalThis.SbbPopoverElement = SbbPopoverElement;
|
|
197
200
|
globalThis.SbbRadioButtonElement = SbbRadioButtonElement;
|
package/lean-off-brand-theme.css
CHANGED
|
@@ -1741,6 +1741,28 @@ summary {
|
|
|
1741
1741
|
--sbb-selection-panel-suffix-color: var(--sbb-color-3);
|
|
1742
1742
|
--sbb-selection-panel-subtext-color: var(--sbb-color-granite);
|
|
1743
1743
|
--sbb-selection-panel-subtext-color: light-dark(var(--sbb-color-granite), var(--sbb-color-smoke));
|
|
1744
|
+
--sbb-pearl-chain-color: var(--sbb-color-3);
|
|
1745
|
+
--sbb-pearl-chain-stroke-width: var(--sbb-border-width-2x);
|
|
1746
|
+
--sbb-pearl-chain-past-color: var(--sbb-color-metal);
|
|
1747
|
+
--sbb-pearl-chain-past-color: light-dark(var(--sbb-color-metal), var(--sbb-color-granite));
|
|
1748
|
+
--sbb-pearl-chain-irrelevant-color: var(--sbb-color-metal);
|
|
1749
|
+
--sbb-pearl-chain-irrelevant-color: light-dark(var(--sbb-color-metal), var(--sbb-color-granite));
|
|
1750
|
+
--sbb-pearl-chain-irrelevant-stroke-width: var(--sbb-border-width-1x);
|
|
1751
|
+
--sbb-pearl-chain-disruption-color: var(--sbb-color-error);
|
|
1752
|
+
--sbb-pearl-chain-walk-color: var(--sbb-color-sky);
|
|
1753
|
+
--sbb-pearl-chain-now-color: var(--sbb-color-primary);
|
|
1754
|
+
--sbb-pearl-chain-now-size: var(--sbb-pearl-chain-bullet-size);
|
|
1755
|
+
--sbb-pearl-chain-node-size: 0.75em;
|
|
1756
|
+
--sbb-pearl-chain-bullet-size: 0.625em;
|
|
1757
|
+
--sbb-pearl-chain-bullet-stop-size: 0.5em;
|
|
1758
|
+
--sbb-pearl-chain-bullet-stop-stroke-width: var(--sbb-pearl-chain-stroke-width);
|
|
1759
|
+
--sbb-pearl-chain-bullet-exceptional-skip-size: 0.75em;
|
|
1760
|
+
--sbb-pearl-chain-bullet-color: var(--sbb-pearl-chain-color);
|
|
1761
|
+
--sbb-pearl-chain-bullet-past-color: var(--sbb-pearl-chain-past-color);
|
|
1762
|
+
--sbb-pearl-chain-bullet-irrelevant-color: var(--sbb-pearl-chain-irrelevant-color);
|
|
1763
|
+
--sbb-pearl-chain-bullet-disruption-color: var(--sbb-color-error);
|
|
1764
|
+
--sbb-pearl-chain-bullet-stroke-width: var(--sbb-pearl-chain-stroke-width);
|
|
1765
|
+
--sbb-pearl-chain-bullet-now-animation-opacity: 0.2;
|
|
1744
1766
|
--sbb-popover-arrow-size: var(--sbb-spacing-fixed-4x);
|
|
1745
1767
|
--sbb-popover-max-width: calc(100% - var(--sbb-spacing-fixed-2x));
|
|
1746
1768
|
--sbb-popover-border-radius: var(--sbb-border-radius-8x);
|