tickslate 0.0.0-stage → 0.1.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 ADDED
@@ -0,0 +1,195 @@
1
+ # Changelog
2
+
3
+ All notable changes to Tickslate are recorded here. The format follows
4
+ [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and versions follow
5
+ [Semantic Versioning](https://semver.org/).
6
+
7
+ ## 0.1.0 — Unreleased
8
+
9
+ The first release. The date is filled in when it is published. Version 0.0.0
10
+ was only ever a private scaffold, so nothing is breaking for anyone who has
11
+ installed from npm. The Changed and Breaking entries below describe how this
12
+ release differs from the development builds that came before it, for anyone
13
+ who tried the repository early.
14
+
15
+ ### Added
16
+
17
+ A grid that works from markup
18
+
19
+ - `<ts-grid>`: lays out its children as a resizable dashboard that reflows
20
+ across 4, 3 and 2 columns, with phone and tablet reflow and gap-filling
21
+ placement that matches CSS `grid-auto-flow: dense`. With no `layout` set,
22
+ every child becomes a tile. Its type comes from the tag, its size from the
23
+ child's `size` attribute (else the first size it supports), and its id from
24
+ `item`. The grid keeps the user's arrangement when the host re-renders.
25
+ Setting `layout` to a `Layout` or an array of items switches to explicit mode.
26
+ - Per-child sizing: each child's `spec`, or its `sizes` and `label` attributes,
27
+ decides what it snaps to, so two children of one type can differ.
28
+ - Market and closed layouts that switch on a `schedule`, with `override()` for
29
+ the manual switch and a replaceable `clock`.
30
+ - `persistLayout(grid, { key, defaults?, storage? })`: loads the user's layout
31
+ for the current device, saves each `ts-layout-change`, switches between phone
32
+ and desktop entries, repairs corrupt data and treats throwing storage as
33
+ empty. Returns `{ reset(device?), dispose() }`.
34
+ - Events: `ts-layout-change` (with `items`, `previous`, and on a remove `index`
35
+ and `item`; reasons include `'undo'` and `'add'`), `ts-columns-change` (with
36
+ `initial`, fired once after the first measure), `ts-layout-issues`,
37
+ `ts-edit-change` and `ts-action`.
38
+ - Core: `readLayout` (which also takes a plain array, and
39
+ `{ unknown: 'keep' }` to keep items no catalog knows), pure editing
40
+ operations, `dropIndex`, `sizeForExtent`, `DEFAULT_PROFILE`, `typeForTag`,
41
+ and catalogs given as a function `(item) => spec | null`.
42
+
43
+ Editing that is safe and accessible
44
+
45
+ - Edit like an iPhone home screen: long press or `editing` to start, drag with
46
+ live reflow, a resize handle that snaps between supported sizes, remove
47
+ badges, edge auto-scroll and full keyboard support.
48
+ - Built-in controls, so no host has to build UI: a visually hidden "Edit layout"
49
+ button that is the grid's first Tab stop, and a floating edit pill
50
+ (`part="edit-pill"`) with Done and "Add back (n)". Hide them with `locked` or
51
+ `edit-controls="off"`.
52
+ - `locked`: a read-only dashboard.
53
+ - `grid.undo()` (20 steps per profile, Ctrl or Cmd+Z while editing) and an undo
54
+ toast after a remove (`part="toast"`, `undo-toast="off"` to suppress).
55
+ `grid.hiddenItems` and `grid.restore(id)` bring removed tiles back.
56
+ - Tap-to-place for users who cannot drag (WCAG 2.5.7), shared with the keyboard
57
+ moves, plus polite status announcements for every edit and focus handling
58
+ that survives reorders, removes and the end of editing.
59
+ - An empty board message ("No widgets on this layout yet", replaceable through
60
+ `slot="empty"`), with Add back when tiles are hidden.
61
+ - Development warnings (`tickslate:` prefix) for the common integration
62
+ mistakes: an explicit-mode child with no `item`, a layout item with no
63
+ child, a dashed tag that is never defined, and repeated generated ids.
64
+
65
+ Widgets
66
+
67
+ - `<ts-card>`: a themed tile for any content, including another library's
68
+ chart, at all seven sizes. Slots `value`, `actions`, `footer` and `empty`; the
69
+ default slot is a body that a `height: 100%` child fills. Attributes `label`,
70
+ `sizes`, `live`, `as-of` and `scrollable`.
71
+ - `<ts-metric>`: a KPI tile at S, Wide and M with a signed change (from `delta`,
72
+ `deltaPct` or `previous`), `goodWhen`, a sparkline, a target bar, and
73
+ `number`, `money`, `percent` and `duration` formats plus `unit` and `compact`.
74
+ - `<ts-chart>`: line and area trends for up to four series at Wide, M, L and XL,
75
+ from `{ series }` or `{ rows, x, y }`, with a legend, gridlines and axis labels
76
+ by size. The kind never relies on colour alone.
77
+ - `<ts-today-pnl>`, `<ts-equity-curve>` and `<ts-session-clock>`: the finance
78
+ set, with an intraday curve, peak and max drawdown with range buttons, and
79
+ exchange session lanes.
80
+ - The widget base `TsWidget` for your own widgets: open shadow root, shared
81
+ themed CSS, `data`, `size`, `label`, `clock`, `static spec`,
82
+ `static dataShape`, one render per microtask and `every()` for timers that
83
+ pause while the page is hidden. See `docs/guide/your-own-widget.md`.
84
+ - Every widget has `spec`, `tickslateVersion` and the loading, empty and error
85
+ views below.
86
+
87
+ Data and states
88
+
89
+ - Loading, empty and error views on every widget. Data never set shows a
90
+ skeleton and `aria-busy`; `data = null` shows the empty state; the `state`
91
+ attribute and `error` property show the error view, with Retry when `actions`
92
+ includes `'retry'`. A `slot="empty"` child replaces the empty text.
93
+ - The `data` attribute: a value starting with `{` or `[` is parsed as JSON, so a
94
+ server template can pass data with no script.
95
+ - `toTime(v)` and `toDay(v, timeZone?)`: instants as epoch seconds or
96
+ milliseconds, a `Date`, an ISO 8601 string (including six-digit microseconds)
97
+ or a numeric string. `toNumber` accepts decimal-library objects with a
98
+ `toNumber()` method. Time zones flow through `equitySeries`, `equityStats`
99
+ and `filterRange`.
100
+ - Development checks against each widget's `dataShape`, naming the element, its
101
+ `item`, the path and what arrived; warnings for invalid JSON, an invalid
102
+ `locale`, `currency` or `time-zone`, dropped rows and data still unset after
103
+ 10 s. A render failure logs the element and a docs link, in production too.
104
+ - Formatting: `createFormatter` (signed money with the true minus sign, lakh
105
+ grouping, compact forms, prices, percents, 24-hour times, durations, `fit`)
106
+ and `configure` for defaults. `fmt.unit(value, unit)` formats units, and
107
+ `fit` takes `{ style: 'money' | 'number' | 'percent' }`. Invalid options fall
108
+ back to the defaults and are listed in `formatter.issues`.
109
+ - Session calendars: `createCalendar` with holidays and special sessions,
110
+ `state`, `nextChange`, `candleClose` and `windows`; NSE and MCX presets;
111
+ `activeProfile` and `nextProfileChange`.
112
+
113
+ Install and theming
114
+
115
+ - Tiles look finished with only `import 'tickslate/elements'`: the default
116
+ tokens are adopted in `@layer tickslate.defaults` when the page has none, and
117
+ anything you define wins. Before the elements upgrade, tiles reserve their
118
+ height so the page does not jump.
119
+ - Theme through `--ts-*` tokens only. Paper (light) and Graphite (dark), light
120
+ and dark islands with `data-theme`, `--ts-axis`, `--ts-focus` and
121
+ `--ts-series-1` to `--ts-series-6`, plus the layout tokens `--ts-row-compact`,
122
+ `--ts-gap-compact`, `--ts-num-weight`, `--ts-shadow-lift`, `--ts-motion` and
123
+ `--ts-ease`.
124
+ - A development build: `tickslate/elements` resolves to `elements.dev.js` under
125
+ the `development` export condition, with every warning. Production builds
126
+ resolve `elements.js`, which has none.
127
+ - Types for everything, including `grid.d.ts` and `widget.d.ts`.
128
+
129
+ Documentation
130
+
131
+ - A README from install to a saved dashboard, framework recipes for React,
132
+ Vue, Svelte and Angular, and guides for saving layouts, server templates and
133
+ bringing your own chart.
134
+ - Reference pages for every element, every core export, every event and every
135
+ theme token, in `docs/`.
136
+ - `examples/` (quickstart, generic dashboard, bare), a playground and a widget
137
+ gallery.
138
+
139
+ ### Changed
140
+
141
+ - Dark mode follows the page, not the OS, by default; see Breaking.
142
+ - `--ts-font-num` falls back to a system sans stack after Newsreader instead of
143
+ Georgia, whose digits are old-style and proportional.
144
+ - `fit` tries the full form, then (for `number` and `percent`) no decimals, then
145
+ compact, and returns the compact form only when it is shorter. Money still
146
+ goes straight from full to compact.
147
+ - `<ts-grid>` reads every layout with `unknown: 'keep'`, so a tile whose tag is
148
+ not defined yet survives a save, and fires `ts-layout-issues` only when the
149
+ host sets `layout` or `catalog`.
150
+ - A widget with no label is named by its tag, and registering a tag that
151
+ another class owns warns in development.
152
+ - The edit chrome is a plain container with three sibling buttons (handle,
153
+ Remove, Resize); managed tiles are `inert` while editing; focus rings are
154
+ `outline`, so they survive forced colours; Escape ends editing only when
155
+ focus is inside the grid.
156
+ - Double-clicking a remove badge, or pressing Delete twice, removes one tile.
157
+ - Requires Node 20 or later wherever Node is used (`engines`).
158
+ - The dark theme is called Graphite in the docs and demos. `data-theme="dark"`,
159
+ `.dark` and every token name are unchanged.
160
+
161
+ ### Breaking
162
+
163
+ - OS dark mode is opt-in. With no `data-theme` and no `.dark` class, tiles stay
164
+ light whatever `prefers-color-scheme` says. Set `data-theme="auto"` on
165
+ `<html>` or any ancestor to follow the OS.
166
+ - A widget whose `data` was never set (`undefined`) shows a loading skeleton,
167
+ not "No data". Set `data = null` to show the empty state. A view model that
168
+ throws shows the error view instead of the empty state.
169
+ - A number below 1e11 in an instant field is read as epoch seconds, not
170
+ milliseconds. Millisecond values from before March 1973 would be misread;
171
+ send an ISO string for those.
172
+ - `toNumber` is stricter: `'0x10'`, `'0b1'`, `'Infinity'` and `''` are `NaN`.
173
+ Only decimal and exponent strings parse.
174
+ - The grid's edit chrome markup is `.chrome > .handle + .remove + .resize`. A
175
+ host reaching into the shadow root for `.chrome[role=button]` must update.
176
+ Attributes, parts and events are unchanged.
177
+ - Hosts that listen for `ts-layout-change` should expect the new reasons
178
+ `'undo'` and `'add'`.
179
+ - The playground's old saved layout (`tickslate-playground-layout`) is no
180
+ longer read.
181
+
182
+ ### Internal
183
+
184
+ - Tests run with `node --test` and need no install. Real-browser tests run
185
+ with `npm run test:browser` in Chromium, WebKit and an iPhone profile,
186
+ including axe checks over every widget and the grid in both themes.
187
+ - `npm run release` runs test, build:lib, build:dev, build:umd, pack:lib,
188
+ verify:package and size. `verify:package` checks the manifest, exports,
189
+ version agreement, DOM-free imports and the tarball allowlist. `npm run size`
190
+ enforces gzipped budgets of 9.5 kB for the core and the UMD build and 50 kB
191
+ for `tickslate/elements` with its core, each measured plus 10 %.
192
+ - `docs/rfc/0002-integration-and-ux.md` is the approved contract for this
193
+ release, `docs/ACCESSIBILITY.md` the definition of done for every widget, and
194
+ `docs/RELEASING.md` the maintainer's publish steps.
195
+ - Tests, docs and demos use made-up figures only.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Shreeshyla Hanasoge
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,243 @@
1
- # Temporary Holding Version
1
+ # Tickslate
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Dashboard widgets for the web: a resizable grid, KPI tiles, charts and cards
4
+ for any data, with a finance set built in.
5
+
6
+ The widgets are custom elements, so the same code runs in plain HTML, Vue,
7
+ React, Svelte and Angular. The core has no runtime dependencies, every colour
8
+ is a `--ts-*` CSS variable, and your app passes the data in: Tickslate never
9
+ fetches anything.
10
+
11
+ - **A grid users can rearrange.** Seven fixed sizes on a 4-column grid that
12
+ drops to 3 columns on tablets and 2 on phones. Long-press to edit, drag to
13
+ move, drag the corner to resize, and Undo or Add back after a removal.
14
+ - **Generic widgets.** `<ts-metric>` (a KPI tile with change, sparkline and
15
+ target), `<ts-chart>` (line and area trends) and `<ts-card>` (a themed tile
16
+ for any content, such as your own Chart.js or ECharts chart).
17
+ - **A finance set.** Today's P&L, an equity curve and an exchange session
18
+ clock, with more to come.
19
+ - **Accessible by default.** Each widget is a labelled region, charts state
20
+ their result in words, gains and losses carry a sign as well as a colour, and
21
+ motion respects `prefers-reduced-motion`. See [docs/ACCESSIBILITY.md](docs/ACCESSIBILITY.md).
22
+
23
+ ## Install
24
+
25
+ ```bash
26
+ npm install tickslate
27
+ ```
28
+
29
+ ```js
30
+ import 'tickslate/elements' // registers <ts-grid> and the widgets
31
+ ```
32
+
33
+ Without a bundler, load it from jsDelivr. Pin an exact version so a release
34
+ can never change your page. This works once 0.1.0 is published to npm.
35
+
36
+ ```html
37
+ <script type="module" src="https://cdn.jsdelivr.net/npm/tickslate@0.1.0/elements.js"></script>
38
+ ```
39
+
40
+ To use a named export from a page script, import it from the same URL:
41
+ `import { persistLayout } from 'https://cdn.jsdelivr.net/npm/tickslate@0.1.0/elements.js'`.
42
+
43
+ ### Fonts (optional)
44
+
45
+ Text is set in Geist and figures in Newsreader. Both are optional: without
46
+ them the tiles fall back to the system UI font, with tabular digits so live
47
+ numbers don't jitter. To use them, load them yourself:
48
+
49
+ ```html
50
+ <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Geist:wght@400;500;600&family=Newsreader:wght@500;600&display=swap">
51
+ ```
52
+
53
+ Or point `--ts-font-ui` and `--ts-font-num` at your own fonts (see Theming).
54
+
55
+ ### tokens.css
56
+
57
+ The elements carry their default theme, so tiles look finished with no
58
+ stylesheet at all. Linking `tokens.css` is still the clearest way to see and
59
+ override every variable, and it always beats the built-in defaults:
60
+
61
+ ```html
62
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/tickslate@0.1.0/tokens.css">
63
+ ```
64
+
65
+ ```js
66
+ import 'tickslate/tokens.css'
67
+ ```
68
+
69
+ ## Quick start
70
+
71
+ Markup is the whole layout: the grid reads its children, in order. The
72
+ `persistLayout` line saves what users change and restores it on the next visit.
73
+
74
+ <!-- quickstart -->
75
+ ```html
76
+ <ts-grid>
77
+ <ts-metric label="Signups" size="S" data='{"value": 1204, "previous": 1075, "compareLabel": "vs last week"}'></ts-metric>
78
+ <ts-chart label="Revenue" size="Wide" data='{"series": [{"name": "Revenue", "points": [{"x": "2026-09-01", "y": 11800}, {"x": "2026-09-02", "y": 12400}, {"x": "2026-09-03", "y": 12100}]}]}'></ts-chart>
79
+ <ts-card label="Notes" sizes="S Wide M"><p>Deploy freeze from Friday.</p></ts-card>
80
+ </ts-grid>
81
+ <script type="module">
82
+ import { persistLayout } from 'tickslate/elements'
83
+ persistLayout(document.querySelector('ts-grid'), { key: 'ops-dashboard' })
84
+ </script>
85
+ ```
86
+ <!-- /quickstart -->
87
+
88
+ [examples/quickstart.html](examples/quickstart.html) is this snippet as a page.
89
+ Run `npm run dev` and open `/examples/quickstart.html`. All figures in this
90
+ repository are made up.
91
+
92
+ ## Data in
93
+
94
+ Every widget renders the `data` you give it. Set it as a property (any
95
+ framework can) or, in server-rendered pages, as a JSON attribute.
96
+
97
+ ```js
98
+ document.querySelector('ts-metric').data = { value: 1204, previous: 1075 }
99
+ ```
100
+
101
+ | You set | The tile shows |
102
+ |---|---|
103
+ | Nothing yet (`undefined`) | A loading skeleton shaped like the content |
104
+ | `null` | The empty state ("No data yet"), or your `slot="empty"` content |
105
+ | `state = 'loading'` | The skeleton again, over existing data, during a re-fetch |
106
+ | `error = 'Could not load'` | An error message, and a Retry button if `actions = ['retry']` |
107
+ | An object | The content |
108
+
109
+ Dates and times can be ISO strings, `Date`s or numbers (below 1e11 means
110
+ seconds). Decimal strings such as `"12.50"` are read as numbers, because some
111
+ backends send decimals that way.
112
+
113
+ ### JSON attributes
114
+
115
+ For Blade, Django, Rails and static pages, put the data in the `data`
116
+ attribute. A value starting with `{` or `[` is parsed as JSON; once you set the
117
+ `data` property, the attribute is ignored. A parse error shows the error view
118
+ and, in development builds, a console warning with the position. See
119
+ [docs/guide/server-templates.md](docs/guide/server-templates.md).
120
+
121
+ ```html
122
+ <ts-metric label="Signups" data='{"value": 1204, "previous": 1075}'></ts-metric>
123
+ ```
124
+
125
+ ## Saving layouts
126
+
127
+ ```js
128
+ import { persistLayout } from 'tickslate/elements'
129
+
130
+ persistLayout(document.querySelector('ts-grid'), { key: 'ops-dashboard' })
131
+ ```
132
+
133
+ This loads the saved layout for the current device (desktop and phone are kept
134
+ apart), saves every change to `localStorage`, and ignores storage that is full
135
+ or blocked. It returns `{ reset(device?), dispose() }`. To save somewhere else,
136
+ listen for `ts-layout-change` and store `event.detail.layout`. See
137
+ [docs/guide/saving-layouts.md](docs/guide/saving-layouts.md).
138
+
139
+ ## Edit mode
140
+
141
+ Editing is on by default. A long press (or the visually hidden **Edit layout**
142
+ button, the grid's first Tab stop) starts it. While editing, a floating pill
143
+ offers **Done** and, once a tile has been removed, **Add back (n)**. Tapping
144
+ outside the grid, or pressing Escape, ends it. Removing a tile shows an Undo
145
+ toast, and Ctrl or Cmd+Z undoes while editing.
146
+
147
+ | Attribute or property | Effect |
148
+ |---|---|
149
+ | `locked` | A read-only dashboard: no edit mode and no controls |
150
+ | `edit-controls="off"` | Hides the built-in button, pill and Add back, for hosts with their own UI. Long press still works |
151
+ | `undo-toast="off"` | No toast after a removal |
152
+ | `editing` | Starts or ends edit mode from code |
153
+ | `grid.hiddenItems`, `grid.restore(id)`, `grid.undo()` | Build your own Add back and Undo |
154
+
155
+ ## Events
156
+
157
+ All events bubble and are composed, so you can listen on the grid or higher up.
158
+
159
+ | Event | Fired by | `detail` |
160
+ |---|---|---|
161
+ | `ts-layout-change` | `<ts-grid>`, after an edit | `{ layout, items, previous, profile, reason, id, index?, item? }`; `reason` is `move`, `resize`, `remove`, `add` or `undo`. Save `layout` |
162
+ | `ts-layout-issues` | `<ts-grid>`, when a layout you set was repaired | `{ issues }` |
163
+ | `ts-edit-change` | `<ts-grid>` | `{ editing }` |
164
+ | `ts-columns-change` | `<ts-grid>`, also once after the first measure | `{ columns, device, initial }` |
165
+ | `ts-profile-change` | `<ts-grid>`, when a schedule or override switches profile | `{ profile, reason }` |
166
+ | `ts-action` | any widget | `{ action, ... }`, such as `retry` or an equity curve's `range` |
167
+
168
+ ## Theming
169
+
170
+ Dark mode is opt-in, and every colour, radius and font is a variable. Override
171
+ a few anywhere in your CSS:
172
+
173
+ ```css
174
+ :root {
175
+ --ts-accent: #0b6bcb;
176
+ --ts-surface: #fff;
177
+ --ts-radius: 6px;
178
+ --ts-font-ui: 'Inter', system-ui, sans-serif;
179
+ }
180
+ ```
181
+
182
+ Set `data-theme="auto"` on `<html>` to follow the operating system, or
183
+ `data-theme="dark"` (or a `.dark` class) for the dark theme, called Graphite.
184
+ `data-theme="light"` makes a light island inside a dark page. The light theme
185
+ is called Paper.
186
+
187
+ ## Browser support
188
+
189
+ Chrome 111, Safari 16.2 and Firefox 113 or later: the grid needs custom
190
+ elements, shadow DOM, `ResizeObserver`, `color-mix()` and `:where()`.
191
+ Server-side rendering is safe: importing the package where there is no DOM
192
+ registers nothing.
193
+
194
+ ## ESM only
195
+
196
+ The package is ES modules only. If Jest has to transform it, let it through:
197
+
198
+ ```js
199
+ transformIgnorePatterns: ['/node_modules/(?!tickslate/)']
200
+ ```
201
+
202
+ ## Guides
203
+
204
+ - Frameworks: [React](docs/guide/frameworks/react.md),
205
+ [Vue](docs/guide/frameworks/vue.md),
206
+ [Svelte](docs/guide/frameworks/svelte.md),
207
+ [Angular](docs/guide/frameworks/angular.md)
208
+ - [Saving layouts](docs/guide/saving-layouts.md)
209
+ - [Your own widget](docs/guide/your-own-widget.md)
210
+ - Reference: [widgets](docs/widgets/), [core](docs/reference/core.md),
211
+ [events](docs/reference/events.md), [theming](docs/theming.md)
212
+ - [Server templates](docs/guide/server-templates.md) (Blade, Django)
213
+ - [Bring your own chart](docs/guide/bring-your-own-chart.md) (Chart.js, ECharts)
214
+ - The contract: [RFC 0001](docs/rfc/0001-api.md) and
215
+ [RFC 0002](docs/rfc/0002-integration-and-ux.md). Data shapes are in the
216
+ `.d.ts` files: [elements](src/elements/index.d.ts),
217
+ [grid](src/elements/grid.d.ts) and [core](src/core/index.d.ts).
218
+
219
+ ### Widgets
220
+
221
+ | Tag | Sizes | For |
222
+ |---|---|---|
223
+ | `<ts-metric>` | S, Wide, M | A KPI with change, sparkline and target |
224
+ | `<ts-chart>` | Wide, M, L, XL | Line and area trends (rows or series) |
225
+ | `<ts-card>` | all seven | Any content, such as your own chart |
226
+ | `<ts-today-pnl>` | S, Wide, M | Today's net, realized and unrealized P&L |
227
+ | `<ts-equity-curve>` | Wide, M, XL | Cumulative P&L with ranges and drawdown |
228
+ | `<ts-session-clock>` | S, Banner | Exchange session status |
229
+
230
+ ## Development
231
+
232
+ ```bash
233
+ npm install
234
+ npm run dev # playground at http://localhost:5173
235
+ npm test # node --test, no install needed
236
+ npm run release # test, build ESM + UMD, assemble dist-lib/
237
+ ```
238
+
239
+ Publishing is done from the assembled folder: `npm publish ./dist-lib`.
240
+
241
+ ## Licence
242
+
243
+ MIT © Shreeshyla Hanasoge
package/elements.d.ts ADDED
@@ -0,0 +1,214 @@
1
+ import type { Calendar, DayResult, Layout, LayoutItem, Numeric, SizeName, Time } from './index.js'
2
+ import { TsWidget } from './widget.js'
3
+ import type { ViewModelContext } from './widget.js'
4
+
5
+ export { version } from './index.js'
6
+ // widget.d.ts is shipped beside this file (scripts/pack-lib.mjs), so this
7
+ // import resolves both in the repo and in the published package.
8
+ export { TsWidget } from './widget.js'
9
+ export type { ViewModelContext, WidgetState } from './widget.js'
10
+ // grid.d.ts is shipped beside this file too (scripts/pack-lib.mjs).
11
+ export * from './grid.js'
12
+ import type { TsGrid } from './grid.js'
13
+
14
+ export interface PersistLayoutOptions {
15
+ /** Each device is stored under `${key}:${device}`. */
16
+ key: string
17
+ /** A Layout or item array serves both devices; in the object form `phone` falls back to `desktop`. */
18
+ defaults?: Layout | LayoutItem[] | { desktop: Layout | LayoutItem[]; phone?: Layout | LayoutItem[] }
19
+ /** Defaults to `localStorage` when it exists. Storage that throws is treated as empty. */
20
+ storage?: Pick<Storage, 'getItem' | 'setItem' | 'removeItem'> | null
21
+ }
22
+
23
+ /** Loads the grid's layout for its device, saves every `ts-layout-change`, and reloads when the device changes. */
24
+ export declare function persistLayout(
25
+ grid: TsGrid,
26
+ options: PersistLayoutOptions
27
+ ): { reset(device?: 'desktop' | 'phone'): void; dispose(): void }
28
+
29
+ /** Freshness, allowed on every widget's data (RFC 0001 §8.1). */
30
+ export interface WidgetFreshness {
31
+ /** When the data was taken: any Time, read with toTime (RFC 0002 §4.1). */
32
+ asOf?: Time
33
+ /** Shows the pulsing dot. */
34
+ live?: boolean
35
+ }
36
+
37
+ export interface CardData extends WidgetFreshness {}
38
+
39
+ /**
40
+ * <ts-card>, every size. Slots: default (the body), value, actions, footer and empty.
41
+ * Attributes: label, sizes (whitespace-separated, default all seven), live, as-of, scrollable.
42
+ * `data` only updates the header; `data = null` never replaces slotted content.
43
+ */
44
+ export declare class TsCard extends TsWidget<CardData> {
45
+ static dataOptional: true
46
+ /** From the sizes and label attributes. */
47
+ get spec(): { label: string; sizes: SizeName[] }
48
+ }
49
+
50
+ export interface MetricData extends WidgetFreshness {
51
+ value: Numeric
52
+ /** Default 'number'. 'percent' takes a fraction (0.12 is 12%); 'duration' takes milliseconds. */
53
+ format?: 'number' | 'money' | 'percent' | 'duration'
54
+ decimals?: number
55
+ /** Formatted with `fmt.unit`: '182 ms', '3 seats'. */
56
+ unit?: string
57
+ /** '1.2M' at every size. Without it, S still goes compact when the figure is too long. */
58
+ compact?: boolean
59
+ /** With format 'percent': value, previous, delta and target are 0 to 100. */
60
+ percentPoints?: boolean
61
+ previous?: Numeric
62
+ /** The absolute change; wins over `previous`. */
63
+ delta?: Numeric
64
+ /** The change as a fraction (0.12 is 12%); always a fraction, whatever `percentPoints` says. */
65
+ deltaPct?: Numeric
66
+ /** Shown after the delta, e.g. 'vs last week'. */
67
+ compareLabel?: string
68
+ /** Which way is good, for the delta's colour. Default 'up'. */
69
+ goodWhen?: 'up' | 'down' | 'neither'
70
+ /** Wide and M: the sparkline. Plain numbers are spaced evenly. */
71
+ series?: Array<{ t: Time; v: Numeric }> | Numeric[]
72
+ /** M: the bar's goal; no bar when 0 or less. */
73
+ target?: Numeric
74
+ /** M: one line under the bar. */
75
+ caption?: string
76
+ }
77
+
78
+ export interface MetricModel {
79
+ label: string
80
+ live: boolean
81
+ time: string
82
+ sparkLabel: string
83
+ ariaLabel: string
84
+ value: string
85
+ delta: string
86
+ arrow: '▲' | '▼' | ''
87
+ tone: 'pos' | 'neg' | 'mut'
88
+ compare: string
89
+ line: string
90
+ area: string
91
+ targetPct: string
92
+ targetWidth: number
93
+ caption: string
94
+ dropped: number
95
+ }
96
+
97
+ /** <ts-metric>, sizes S · Wide · M. */
98
+ export declare class TsMetric extends TsWidget<MetricData> {
99
+ /** Pure: data to display strings, paths and the accessible name. */
100
+ static metricModel(data: MetricData, variant: SizeName, ctx: ViewModelContext): MetricModel
101
+ }
102
+
103
+ export type ChartKind = 'line' | 'area'
104
+
105
+ export interface ChartSeries {
106
+ id?: string
107
+ name: string
108
+ /** Default 'line'. */
109
+ kind?: ChartKind
110
+ /** Read in any order; x with `toTime` (or `toNumber` when `xType` is 'number'), y with `toNumber`. */
111
+ points: Array<{ x: Time; y: Numeric }>
112
+ }
113
+
114
+ export interface ChartOptions extends WidgetFreshness {
115
+ /** How x is read. Default 'time'. */
116
+ xType?: 'time' | 'number'
117
+ /** How each series' last value is shown. Default 'number'; 'percent' takes fractions, 'duration' milliseconds. */
118
+ format?: 'number' | 'money' | 'percent' | 'duration'
119
+ decimals?: number
120
+ /** Formatted with `fmt.unit`: '182 ms'. */
121
+ unit?: string
122
+ /** Accepted for parity with `<ts-metric>`; no chart size shows a change yet. */
123
+ goodWhen?: 'up' | 'down' | 'neither'
124
+ }
125
+
126
+ /** Either named series, or rows with the keys to read. At most four series are drawn. */
127
+ export type ChartData = ChartOptions &
128
+ (
129
+ | { series: ChartSeries[] }
130
+ | {
131
+ rows: Array<Record<string, unknown>>
132
+ /** The row key for x. */
133
+ x: string
134
+ y: string | string[] | Array<{ key: string; name?: string; kind?: ChartKind }>
135
+ }
136
+ )
137
+
138
+ export interface ChartModel {
139
+ /** What the svg's `aria-label` says: 'Revenue, last 12,400; Cost, last 8,100'. */
140
+ ariaLabel: string
141
+ series: Array<{ name: string; kind: ChartKind; line: string; area: string; last: string }>
142
+ /** `index` 1 to 4 picks `--ts-series-<n>`. */
143
+ legend: Array<{ name: string; index: number }>
144
+ axis: { start: string; middle: string; end: string }
145
+ /** Where the zero line falls in the 400 by 100 drawing box, drawn in `--ts-axis`. */
146
+ zeroY: number
147
+ /** Rows or points that couldn't be read. */
148
+ dropped: number
149
+ }
150
+
151
+ /** <ts-chart>, sizes Wide · M · L · XL. Lines and areas, up to four series, legend from M up. */
152
+ export declare class TsChart extends TsWidget<ChartData> {
153
+ /** Pure: data to paths, labels and the accessible name. */
154
+ static chartModel(data: ChartData, variant: SizeName, ctx: ViewModelContext): ChartModel
155
+ }
156
+
157
+ export interface TodayPnlData extends WidgetFreshness {
158
+ /** Shown exactly as given; the widget never recomputes it from the parts. */
159
+ net: Numeric
160
+ realized: Numeric
161
+ unrealized: Numeric
162
+ /** Shown as a deduction whichever way it is signed. */
163
+ fees: Numeric
164
+ closedCount?: number
165
+ openCount?: number
166
+ /** M: the curve. Readings need not arrive in order. */
167
+ intraday?: Array<{ t: Time; v: Numeric }>
168
+ /** M: dots on the curve; ones outside the readings are left out. */
169
+ marks?: Array<{ t: Time; kind: 'entry' | 'exit'; label?: string }>
170
+ /** M: the x-axis span, e.g. today's session from `calendar.hours()`. Defaults to the readings' span. */
171
+ window?: { from: Time; to: Time }
172
+ }
173
+
174
+ /** <ts-today-pnl>, sizes S · Wide · M. */
175
+ export declare class TsTodayPnl extends TsWidget<TodayPnlData> {}
176
+
177
+ export type EquityRange = '1W' | '1M' | '3M' | 'YTD' | 'All'
178
+
179
+ export interface EquityCurveData extends WidgetFreshness {
180
+ /** The daily P&L series; the widget accumulates it. */
181
+ days: DayResult[]
182
+ /** M, XL: for the whole series, so not shown for a range that cuts it. */
183
+ fees?: Numeric
184
+ /** XL: for the whole series, so not shown for a range that cuts it. Falls back to the sum of `days[].trades`. */
185
+ trades?: number
186
+ }
187
+
188
+ /** <ts-equity-curve>, sizes Wide · M · XL. XL's range buttons fire `ts-action` with action 'range'. */
189
+ export declare class TsEquityCurve extends TsWidget<EquityCurveData> {
190
+ /** Reflects the `range` attribute; an unknown value reads as 'All'. */
191
+ range: EquityRange
192
+ }
193
+
194
+ export interface SessionClockData {
195
+ calendar: Calendar
196
+ /** Session ids, one lane each on Banner. S shows the first. */
197
+ sessions: string[]
198
+ /** Banner: candle-close ticks. Only the first two are drawn. */
199
+ timeframes?: number[]
200
+ }
201
+
202
+ /** <ts-session-clock>, sizes S · Banner. Ticks by itself every second; set `clock` to freeze it. */
203
+ export declare class TsSessionClock extends TsWidget<SessionClockData> {}
204
+
205
+ declare global {
206
+ interface HTMLElementTagNameMap {
207
+ 'ts-card': TsCard
208
+ 'ts-metric': TsMetric
209
+ 'ts-chart': TsChart
210
+ 'ts-today-pnl': TsTodayPnl
211
+ 'ts-equity-curve': TsEquityCurve
212
+ 'ts-session-clock': TsSessionClock
213
+ }
214
+ }