gtfs-garage-web 0.0.1

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.
@@ -0,0 +1,97 @@
1
+ /**
2
+ * The row grid: the part that makes IDs navigable.
3
+ *
4
+ * A foreign-key column renders as a link to the referenced table, filtered to
5
+ * the matching row. A table's own id column additionally offers "related"
6
+ * buttons for the reverse direction (a route to its trips, say). The server
7
+ * only reports links whose target exists in the loaded feed, so nothing here
8
+ * leads to a file the feed does not contain.
9
+ */
10
+ import { type Dom } from "../dom";
11
+ import type { Filter, PageResponse, RelatedLink } from "../sources/types";
12
+ import type { AppState } from "../state";
13
+ export interface RelatedButton {
14
+ link: RelatedLink;
15
+ label: string;
16
+ title: string;
17
+ }
18
+ /**
19
+ * How to label the reverse links under an id.
20
+ *
21
+ * A table can point at the same id from more than one column - `transfers` and
22
+ * `pathways` each reference a stop as both `from_stop_id` and `to_stop_id` - so
23
+ * labelling by table name alone renders two buttons that look identical and go
24
+ * to different places. The column is added only where a table appears more than
25
+ * once, which keeps the common label short, and the tooltip always names it.
26
+ */
27
+ export declare function describeRelated(related: RelatedLink[]): RelatedButton[];
28
+ /**
29
+ * Showing a row on the map.
30
+ *
31
+ * Every one is optional, and absent means the viewer has no map: a host that
32
+ * mounts without the map part gets no map buttons rather than buttons that do
33
+ * nothing. Granular rather than one flag because they already differ - a route
34
+ * is only highlightable once its shapes are known.
35
+ */
36
+ export interface HighlightCallbacks {
37
+ onHighlightStop: (stopId: string) => void;
38
+ onHighlightShape: (shapeId: string) => void;
39
+ onHighlightRoute: (routeId: string) => void;
40
+ onHighlightLocation: (locationId: string) => void;
41
+ }
42
+ export interface TableCallbacks extends Partial<HighlightCallbacks> {
43
+ onNavigate: (table: string, filters: Filter[]) => void;
44
+ onPage: (page: number) => void;
45
+ }
46
+ export interface RowAction {
47
+ label: string;
48
+ title: string;
49
+ run: () => void;
50
+ }
51
+ /**
52
+ * Which map buttons a row should carry.
53
+ *
54
+ * Pure, so the rules are testable without a document - the same split the rest
55
+ * of this package uses, where `values.ts` decides and `table.ts` draws. A row
56
+ * offers an action only when it has the id for it *and* something can act on
57
+ * it.
58
+ */
59
+ export declare function rowActions(values: Record<string, string | null>, context: {
60
+ table: string;
61
+ hasRouteShapes: boolean;
62
+ }, callbacks: Partial<HighlightCallbacks>): RowAction[];
63
+ export declare class TableView {
64
+ private readonly dom;
65
+ private readonly state;
66
+ private readonly callbacks;
67
+ /**
68
+ * The page currently on screen, so that a display change can redraw without
69
+ * asking the server again - on a large feed a refetch to toggle formatting
70
+ * would be a needless round trip over millions of rows.
71
+ */
72
+ private lastPage;
73
+ constructor(dom: Dom, state: AppState, callbacks: TableCallbacks);
74
+ /** Draw the current page again, picking up a changed display preference. */
75
+ redraw(): void;
76
+ showMessage(text: string): void;
77
+ render(page: PageResponse): void;
78
+ private buildRow;
79
+ private buildCell;
80
+ /**
81
+ * Draw what `renderValue` decided.
82
+ *
83
+ * Every path here writes through `textContent`, never `innerHTML`, so a feed
84
+ * cannot inject markup - and an `href` is only ever set from a URL that
85
+ * module already restricted to http, https and mailto.
86
+ */
87
+ private renderValue;
88
+ /**
89
+ * Whether any row could ever carry an action.
90
+ *
91
+ * Decides the column, not the button: with no map there is nothing to put in
92
+ * it, and an always-empty leading column is worse than no column at all.
93
+ */
94
+ private get hasActions();
95
+ private buildRowActions;
96
+ private renderPager;
97
+ }
@@ -0,0 +1,61 @@
1
+ /**
2
+ * How to draw one cell's value, given what GTFS says the field holds.
3
+ *
4
+ * This decides; `table.ts` draws. Keeping the decision pure is what makes the
5
+ * cases that matter testable at all, since vitest runs without a document and
6
+ * the cases that matter are the malformed ones.
7
+ *
8
+ * Two rules govern everything here:
9
+ *
10
+ * 1. **A value that does not conform is shown as the string the feed contains.**
11
+ * Never blanked, never corrected, never allowed to throw. This is the
12
+ * project's existing principle - a feed with a malformed number still opens,
13
+ * because type errors are what an inspection tool exists to reveal - applied
14
+ * to rendering rather than to loading.
15
+ *
16
+ * 2. **A feed is untrusted input.** Only `http:`, `https:` and `mailto:` ever
17
+ * become a link; anything else is text. Before this module, no feed value
18
+ * was ever used as an `href`, so links are a new attack surface and
19
+ * `javascript:` in a URL field must not survive it.
20
+ */
21
+ import type { ColumnInfo } from "../sources/types";
22
+ export interface Rendered {
23
+ /** What to show. Always a string; never empty unless the value was. */
24
+ text: string;
25
+ kind: "plain" | "numeric" | "link" | "color";
26
+ /** Only ever an http, https or mailto URL. */
27
+ href?: string;
28
+ /** Only ever `#RRGGBB`, from six hex digits. */
29
+ swatch?: string;
30
+ /** What was expected, when the value does not conform. Shown on hover. */
31
+ malformed?: string;
32
+ /**
33
+ * Set when the feed left this field empty and GTFS says what that means. The
34
+ * text is the implied value, not something the producer wrote, so it must be
35
+ * drawn in a way that cannot be mistaken for one.
36
+ */
37
+ implied?: string;
38
+ }
39
+ /**
40
+ * The only entry point, and the one place that can fail safely.
41
+ *
42
+ * Every formatter above is total, so the catch should never fire. It is here so
43
+ * that it cannot matter if a formatter added later is not - one malformed cell
44
+ * in a sixty-million-row feed must not blank a page.
45
+ *
46
+ * `raw` turns all of it off and returns the characters the file contains.
47
+ * Formatting is a reading aid, and a reading aid is the wrong thing when the
48
+ * question is what a producer actually wrote: `Aug 22, 2026` is easier to read
49
+ * than `20260822`, but only the second answers "is this field padded?". An
50
+ * inspection tool has to be able to stop interpreting.
51
+ */
52
+ export declare function renderValue(value: string | null, column: ColumnInfo | undefined, raw?: boolean): Rendered;
53
+ /**
54
+ * What to say about a column in its header tooltip.
55
+ *
56
+ * The schema knows more about a column than its name shows: what GTFS says it
57
+ * holds, whether it must be filled in, and - for a conditionally required
58
+ * field - the condition in words. "Conditionally required" on its own is not
59
+ * something a reader can act on; the condition is.
60
+ */
61
+ export declare function describeColumn(column: ColumnInfo | undefined): string;
package/package.json ADDED
@@ -0,0 +1,94 @@
1
+ {
2
+ "name": "gtfs-garage-web",
3
+ "version": "0.0.1",
4
+ "description": "Browse a GTFS feed: a mountable viewer with a table, filters and a map.",
5
+ "license": "Apache-2.0",
6
+ "type": "module",
7
+ "scripts": {
8
+ "dev": "vite",
9
+ "build": "vite build",
10
+ "typecheck": "tsc --noEmit",
11
+ "test": "vitest run",
12
+ "build:lib": "vite build --config vite.lib.config.ts && yarn types",
13
+ "types": "tsc -p tsconfig.lib.json"
14
+ },
15
+ "devDependencies": {
16
+ "@duckdb/duckdb-wasm": "1.32.0",
17
+ "@types/node": "^22.20.2",
18
+ "@types/react": "^18.3.11",
19
+ "@types/react-dom": "^18.3.1",
20
+ "maplibre-gl": "^5.7.0",
21
+ "react": "^18.3.1",
22
+ "react-dom": "^18.3.1",
23
+ "typescript": "^5.7.2",
24
+ "vite": "^6.0.5",
25
+ "vitest": "^4.1.11"
26
+ },
27
+ "resolutions": {
28
+ "vite": "^6.0.5"
29
+ },
30
+ "repository": {
31
+ "type": "git",
32
+ "url": "git+https://github.com/MobilityData/gtfs-garage.git",
33
+ "directory": "web"
34
+ },
35
+ "homepage": "https://github.com/MobilityData/gtfs-garage",
36
+ "keywords": [
37
+ "gtfs",
38
+ "transit",
39
+ "viewer",
40
+ "maplibre"
41
+ ],
42
+ "files": [
43
+ "dist"
44
+ ],
45
+ "types": "./dist/mount.d.ts",
46
+ "module": "./dist/mount.js",
47
+ "sideEffects": [
48
+ "**/*.css"
49
+ ],
50
+ "exports": {
51
+ ".": {
52
+ "types": "./dist/mount.d.ts",
53
+ "import": "./dist/mount.js"
54
+ },
55
+ "./dataset": {
56
+ "types": "./dist/mount-dataset.d.ts",
57
+ "import": "./dist/dataset.js"
58
+ },
59
+ "./map": {
60
+ "types": "./dist/map-part.d.ts",
61
+ "import": "./dist/map.js"
62
+ },
63
+ "./parquet": {
64
+ "types": "./dist/sources/parquet.d.ts",
65
+ "import": "./dist/parquet.js"
66
+ },
67
+ "./react": {
68
+ "types": "./dist/react.d.ts",
69
+ "import": "./dist/react.js"
70
+ },
71
+ "./style.css": "./dist/gtfs-garage-web.css",
72
+ "./package.json": "./package.json"
73
+ },
74
+ "peerDependencies": {
75
+ "@duckdb/duckdb-wasm": "^1.32.0",
76
+ "maplibre-gl": "^5.0.0",
77
+ "react": "^18 || ^19",
78
+ "react-dom": "^18 || ^19"
79
+ },
80
+ "peerDependenciesMeta": {
81
+ "@duckdb/duckdb-wasm": {
82
+ "optional": true
83
+ },
84
+ "maplibre-gl": {
85
+ "optional": true
86
+ },
87
+ "react": {
88
+ "optional": true
89
+ },
90
+ "react-dom": {
91
+ "optional": true
92
+ }
93
+ }
94
+ }