@autobusal/common 1.15.6 → 1.16.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/CHANGELOG.md CHANGED
@@ -6,6 +6,17 @@ All notable changes to `@autobusal/common` are documented here. This project fol
6
6
  > Note: 1.10.0 through 1.15.1 were published without changelog entries. The
7
7
  > gap is left as-is rather than reconstructed after the fact.
8
8
 
9
+ ## [1.16.0] - 2026-08-01
10
+
11
+ ### Added
12
+
13
+ - **The date field can tell its form when the day changes.** `ViewData.onChange`
14
+ was already honoured by `select` and `checkbox`, but not by `date`/`dob`/
15
+ `picker`, so a form whose other fields depend on the chosen day had no way
16
+ to observe it short of not using `Viewer` at all. `Calendar` now takes an
17
+ optional `onChange(DD/MM/YYYY)` and `Viewer` passes `item.onChange` through
18
+ to it. Purely additive - existing callers are unaffected.
19
+
9
20
  ## [1.15.6] - 2026-08-01
10
21
 
11
22
  ### Changed
@@ -22,9 +22,15 @@ interface Props {
22
22
  minDate?: string
23
23
  refs: UseFormRegister<any>
24
24
  onUpdate: UseFormSetValue<any>
25
+ /**
26
+ * Edited: Ferjolt Ozuni - Date: 2026-08-01
27
+ * Notified with the newly picked DD/MM/YYYY day, for callers that need to
28
+ * react to it rather than only read it back at submit time.
29
+ */
30
+ onChange?: (value: string) => void
25
31
  }
26
32
 
27
- const Calendar = ({ type, name, defaultValue, t, validation, minDate, refs, onUpdate }: Props): JSX.Element => {
33
+ const Calendar = ({ type, name, defaultValue, t, validation, minDate, refs, onUpdate, onChange }: Props): JSX.Element => {
28
34
  const [ show, setShow ] = useState<boolean>(false);
29
35
  const [ prepared, setPrepared ] = useState<string>(defaultValue ?? prepareDate(new Date()));
30
36
  const [ selected, setSelected ] = useState<string>(prepared);
@@ -40,6 +46,8 @@ const Calendar = ({ type, name, defaultValue, t, validation, minDate, refs, onUp
40
46
  setPrepared(prepared);
41
47
 
42
48
  onUpdate(name, prepared);
49
+
50
+ onChange?.(prepared);
43
51
  };
44
52
 
45
53
  return (
@@ -10,8 +10,20 @@ const STOP_UNDEFINED = ['0', '0'];
10
10
  *
11
11
  * Edited: Ferjolt Ozuni - Date: 2026-08-01
12
12
  */
13
- export const hasPosition = (position?: string[]): boolean => {
14
- if (position === undefined || position.length < 2) {
13
+ export const hasPosition = (position?: unknown): boolean => {
14
+ /**
15
+ * Edited: Ferjolt Ozuni - Date: 2026-08-01
16
+ *
17
+ * Array.isArray, not a length check. A stop whose `location` was stored
18
+ * as {lat, lng} instead of the positional ["lat","lng"] has no `.length`,
19
+ * so `position.length < 2` was false and the row sailed through the
20
+ * guard - then `.map` threw and took the whole route page down with
21
+ * "Because of a system error, we can't display this page!".
22
+ *
23
+ * One malformed stop out of fifty-three should cost that stop its pin,
24
+ * not the page.
25
+ */
26
+ if (!Array.isArray(position) || position.length < 2) {
15
27
  return false;
16
28
  }
17
29
 
@@ -42,7 +54,10 @@ export const getCoordinates = (locations: LocationData[]): number[][] => (
42
54
  );
43
55
 
44
56
  export const getPosition = (position: string[] | undefined, type: string = 'normal'): LatLngExpression => {
45
- if (position === undefined) {
57
+ // Edited: Ferjolt Ozuni - Date: 2026-08-01
58
+ // Anything that is not a positional array yields 0,0 rather than
59
+ // [NaN, NaN], which Leaflet rejects by throwing.
60
+ if (!Array.isArray(position)) {
46
61
  return [0, 0];
47
62
  }
48
63
 
package/Road/Road.tsx ADDED
@@ -0,0 +1,53 @@
1
+ import { useState } from 'react';
2
+ import { RiBus2Line } from 'react-icons/ri';
3
+ import { Container, Bus, Vehicle, Fallback } from './styles';
4
+
5
+ interface Props {
6
+ /**
7
+ * Brand asset root (SettingsData.url). The bus is served from the label's
8
+ * own storage, exactly like its logo and hero artwork, so a whitelabel can
9
+ * ship its own vehicle without a code change.
10
+ */
11
+ url?: string
12
+ }
13
+
14
+ /**
15
+ * A road with a bus travelling along it.
16
+ *
17
+ * Ferjolt Ozuni - Date: 2026-08-01
18
+ *
19
+ * Shared by the under-construction holding page and the error pages, which
20
+ * had been growing their own copies of the same CSS.
21
+ *
22
+ * The vehicle is an image from label storage rather than a bundled asset,
23
+ * for the same reason the logo is: every brand gets its own without a
24
+ * rebuild. If the brand has not supplied one the icon stands in, so a
25
+ * missing file degrades to what the page looked like before rather than
26
+ * leaving a hole where a bus should be.
27
+ *
28
+ * Entirely decorative - the caller marks it aria-hidden.
29
+ */
30
+ const Road = ({ url }: Props): JSX.Element => {
31
+ const [ failed, setFailed ] = useState<boolean>(false);
32
+
33
+ return (
34
+ <Container>
35
+ <Bus>
36
+ { url !== undefined && !failed
37
+ ? (
38
+ /* WebP first (10KB against the PNG's 66KB), with the PNG as the
39
+ fallback source and the icon as the fallback for a brand that
40
+ has not supplied artwork at all */
41
+ <picture>
42
+ <source srcSet={ `${ url }/bus-artwork.webp` } type="image/webp" />
43
+
44
+ <Vehicle src={ `${ url }/bus-artwork.png` } alt="" onError={ () => setFailed(true) } />
45
+ </picture>
46
+ )
47
+ : <Fallback><RiBus2Line /></Fallback> }
48
+ </Bus>
49
+ </Container>
50
+ );
51
+ };
52
+
53
+ export default Road;
package/Road/styles.ts ADDED
@@ -0,0 +1,87 @@
1
+ import styled, { keyframes } from 'styled-components';
2
+
3
+ const drift = keyframes`
4
+ from { transform: translateX(0); }
5
+ to { transform: translateX(-84px); }
6
+ `;
7
+
8
+ const travel = keyframes`
9
+ from { transform: translateX(-26vw); }
10
+ to { transform: translateX(114vw); }
11
+ `;
12
+
13
+ /**
14
+ * Ferjolt Ozuni - Date: 2026-08-01
15
+ *
16
+ * A band of road across the foot of the page. Lifted out of
17
+ * UnderConstruction so the error pages could use the same one instead of a
18
+ * second copy of the same CSS.
19
+ */
20
+ export const Container = styled.div`
21
+ position: absolute;
22
+ right: 0;
23
+ bottom: 0;
24
+ left: 0;
25
+ height: 150px;
26
+ overflow: hidden;
27
+ background: linear-gradient(
28
+ to top,
29
+ ${ props => props.theme.primary.normal }22,
30
+ transparent
31
+ );
32
+
33
+ &::after {
34
+ content: '';
35
+ position: absolute;
36
+ bottom: 46px;
37
+ left: 0;
38
+ width: 200%;
39
+ height: 3px;
40
+ border-radius: 3px;
41
+ background: repeating-linear-gradient(
42
+ to right,
43
+ ${ props => props.theme.primary.normal }66 0 42px,
44
+ transparent 42px 84px
45
+ );
46
+ animation: ${ drift } 6s linear infinite;
47
+ }
48
+
49
+ /* a moving road is exactly the kind of thing that should stop for anyone
50
+ who has asked motion to stop */
51
+ @media (prefers-reduced-motion: reduce) {
52
+ &::after {
53
+ animation: none;
54
+ }
55
+ }
56
+ `;
57
+
58
+ export const Bus = styled.div`
59
+ position: absolute;
60
+ bottom: 44px;
61
+ left: 0;
62
+ display: flex;
63
+ align-items: flex-end;
64
+ animation: ${ travel } 18s linear infinite;
65
+
66
+ /* parked, not removed - the scene should still make sense standing still */
67
+ @media (prefers-reduced-motion: reduce) {
68
+ animation: none;
69
+ transform: translateX(10vw);
70
+ }
71
+ `;
72
+
73
+ export const Vehicle = styled.img`
74
+ display: block;
75
+ width: 150px;
76
+ height: auto;
77
+
78
+ @media (min-width: 768px) {
79
+ width: 200px;
80
+ }
81
+ `;
82
+
83
+ export const Fallback = styled.span`
84
+ display: flex;
85
+ font-size: 44px;
86
+ color: ${ props => props.theme.primary.normal };
87
+ `;
@@ -1,6 +1,7 @@
1
1
  import { useEffect } from 'react';
2
2
  import { TFunction } from 'i18next';
3
- import { Screen, Content, Logo, Title, Message, Road } from './styles';
3
+ import Road from '../Road/Road';
4
+ import { Screen, Content, Logo, Title, Message } from './styles';
4
5
  import { useGetSettings } from '@autobusal/providers/services';
5
6
 
6
7
  interface Props {
@@ -75,7 +76,12 @@ const UnderConstruction = ({ t }: Props): JSX.Element => {
75
76
 
76
77
  </Content>
77
78
 
78
- <Road aria-hidden="true" />
79
+ { /* Edited: Ferjolt Ozuni - Date: 2026-08-01
80
+ Shared with the error pages now, and carrying the brand's bus
81
+ rather than a bare strip of road. */ }
82
+ <div aria-hidden="true">
83
+ <Road url={ settings?.url } />
84
+ </div>
79
85
  </Screen>
80
86
  );
81
87
  };
@@ -11,11 +11,6 @@ import styled, { keyframes } from 'styled-components';
11
11
  * rather than a generic maintenance page.
12
12
  */
13
13
 
14
- const drift = keyframes`
15
- from { transform: translate3d(0, 0, 0); }
16
- to { transform: translate3d(-50%, 0, 0); }
17
- `;
18
-
19
14
  const rise = keyframes`
20
15
  from { opacity: 0; transform: translateY(14px); }
21
16
  to { opacity: 1; transform: translateY(0); }
@@ -72,44 +67,3 @@ export const Message = styled.p`
72
67
  color: ${ props => props.theme.font.faded };
73
68
  `;
74
69
 
75
- /**
76
- * A slow band of road at the foot of the page. Purely decorative, and
77
- * hidden from assistive tech - it carries no information.
78
- */
79
- export const Road = styled.div`
80
- position: absolute;
81
- right: 0;
82
- bottom: 0;
83
- left: 0;
84
- height: 96px;
85
- opacity: 0.5;
86
- background: linear-gradient(
87
- to top,
88
- ${ props => props.theme.primary.normal }22,
89
- transparent
90
- );
91
-
92
- &::after {
93
- content: '';
94
- position: absolute;
95
- bottom: 34px;
96
- left: 0;
97
- width: 200%;
98
- height: 3px;
99
- border-radius: 3px;
100
- background: repeating-linear-gradient(
101
- to right,
102
- ${ props => props.theme.primary.normal }66 0 42px,
103
- transparent 42px 84px
104
- );
105
- animation: ${ drift } 6s linear infinite;
106
- }
107
-
108
- /* a moving dashed line is exactly the kind of thing that should stop for
109
- anyone who has asked motion to stop */
110
- @media (prefers-reduced-motion: reduce) {
111
- &::after {
112
- animation: none;
113
- }
114
- }
115
- `;
package/Viewer/Data.tsx CHANGED
@@ -83,6 +83,12 @@ const Data = ({ id, item, refs, t, onUpdate }: Props): (JSX.Element | null) => {
83
83
  validation={ item.rules }
84
84
  refs={ refs }
85
85
  onUpdate={ onUpdate }
86
+ // Edited: Ferjolt Ozuni - Date: 2026-08-01
87
+ // `select` and `checkbox` already surfaced item.onChange; date did
88
+ // not, so a form whose other fields react to the chosen day (e.g.
89
+ // the Telegram broadcast's live recipient count) had no way to
90
+ // observe it short of not using Viewer at all.
91
+ onChange={ item.onChange }
86
92
  />
87
93
  );
88
94
 
package/index.ts CHANGED
@@ -29,6 +29,7 @@ import Policy from './Policy/Policy';
29
29
  import Report from './Report/Report';
30
30
  import RouteFeature from './RouteFeature/RouteFeature';
31
31
  import Required from './Required/Required';
32
+ import Road from './Road/Road';
32
33
  import RouteItem from './RouteItem/RouteItem';
33
34
  import Row from './Table/Row';
34
35
  import Table from './Table/Table';
@@ -72,6 +73,7 @@ export {
72
73
  Report,
73
74
  RouteFeature,
74
75
  Required,
76
+ Road,
75
77
  RouteItem,
76
78
  Row,
77
79
  Table,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@autobusal/common",
3
- "version": "1.15.6",
3
+ "version": "1.16.0",
4
4
  "author": "Ferjolt Ozuni",
5
5
  "type": "module",
6
6
  "main": "index.ts"