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 +195 -0
- package/LICENSE +21 -0
- package/README.md +242 -2
- package/elements.d.ts +214 -0
- package/elements.dev.js +4781 -0
- package/elements.dev.js.map +1 -0
- package/elements.js +4642 -0
- package/elements.js.map +1 -0
- package/grid.d.ts +108 -0
- package/index.d.ts +373 -0
- package/index.js +920 -0
- package/index.js.map +1 -0
- package/package.json +69 -4
- package/tokens.css +178 -0
- package/umd/tickslate.umd.js +2 -0
- package/umd/tickslate.umd.js.map +1 -0
- package/widget.d.ts +113 -0
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
|
-
#
|
|
1
|
+
# Tickslate
|
|
2
2
|
|
|
3
|
-
|
|
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
|
+
}
|