fold-ng 0.7.0 → 0.9.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 +552 -1
- package/README.md +103 -4
- package/devtools/package.json +4 -0
- package/fesm2022/fold-ng-src-devtools.mjs +218 -0
- package/fesm2022/fold-ng-src-devtools.mjs.map +1 -0
- package/fesm2022/fold-ng.mjs +3988 -208
- package/fesm2022/fold-ng.mjs.map +1 -1
- package/llms.txt +14 -3
- package/package.json +12 -2
- package/tmp-esm2022/tsconfig.lib.tsbuildinfo +1 -0
- package/tokens/semantic.css +57 -0
- package/types/fold-ng-src-devtools.d.ts +86 -0
- package/types/fold-ng-src-devtools.d.ts.map +1 -0
- package/types/fold-ng.d.ts +2949 -311
- package/types/fold-ng.d.ts.map +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -8,6 +8,554 @@ All notable changes to **fold-ng** are documented here. The format follows
|
|
|
8
8
|
|
|
9
9
|
_Nothing yet._
|
|
10
10
|
|
|
11
|
+
## [0.9.0] - 2026-08-04
|
|
12
|
+
|
|
13
|
+
### Changed
|
|
14
|
+
|
|
15
|
+
- **BREAKING — the shell owns the content scroll by default; pages flow.** The
|
|
16
|
+
all-in-one scroll model (`docs/scroll.md`), slice A. `fold-app-shell`'s
|
|
17
|
+
`contentScroll="clip" | "auto"` input is **renamed and reshaped** to
|
|
18
|
+
`scroll="scroll" | "stage"`, and the **default flips**: the shell's content
|
|
19
|
+
region now owns the scroll (`scroll`, was `clip`/page-owns), so a
|
|
20
|
+
`fold-page-layout` inside it no longer double-scrolls. `fold-page-layout` gains
|
|
21
|
+
`scroll="flow" | "own"` and **defaults to `flow`** — it owns no scroll box and
|
|
22
|
+
flows inside the shell. Net effect for a normal page: identical, minus the P0
|
|
23
|
+
bug where a `footerBehavior="scroll"` footer sat below an unreachable
|
|
24
|
+
`overscroll-behavior: contain` boundary (this **deletes the LaFolieDouce B2B
|
|
25
|
+
`!important` workaround**). The scroll lives on an **inner** box, never the
|
|
26
|
+
content region itself, so a docked panel anchored to the region stays fixed
|
|
27
|
+
over the frame. Migration: a page that must scroll as a self-contained unit
|
|
28
|
+
(a split view whose shell must not move) sets `fold-app-shell scroll="stage"`
|
|
29
|
+
and/or `fold-page-layout scroll="own"`. A short page still pins a trailing
|
|
30
|
+
`scroll` footer to the bottom (the content grows to fill), replacing the old
|
|
31
|
+
`margin-top: auto` glue.
|
|
32
|
+
|
|
33
|
+
- **`@angular/router` is now a declared (optional) peer dependency.** `fold-view-nav`
|
|
34
|
+
imports `RouterLink`/`RouterLinkActive`, but the package only listed router as a
|
|
35
|
+
devDependency — an undeclared peer that happened to resolve because every Angular
|
|
36
|
+
app ships Router. It's now in `peerDependencies` with `peerDependenciesMeta:
|
|
37
|
+
{ "@angular/router": { optional: true } }`, so apps that use the router-coupled
|
|
38
|
+
nav components (view-nav, and the new breadcrumb / back-link) get a correct
|
|
39
|
+
install signal, while apps that don't aren't forced to add it (those components
|
|
40
|
+
tree-shake out). No API change.
|
|
41
|
+
|
|
42
|
+
- **`FoldPanelHostService.open()` accepts an optional-data panel without a manual
|
|
43
|
+
type widen.** A panel whose `data` input is optional (`data = input<T>()` →
|
|
44
|
+
`InputSignal<T | undefined>`) used to force `open<T | undefined, R>()` to dodge
|
|
45
|
+
a `TS2345` (hit by the LaFolieDouce B2B PickupPanel). `FoldPanelContent<T>.data`
|
|
46
|
+
is now typed as the covariant **read** side (`Signal<T | undefined>`) instead of
|
|
47
|
+
the invariant `InputSignal<T>`, so both a required (`input.required<T>()`) and an
|
|
48
|
+
optional data input satisfy the contract — `open(Cmp, { data })` infers `T` from
|
|
49
|
+
the value with no widen, and the data value stays type-checked. Non-breaking for
|
|
50
|
+
existing panels (an `InputSignal<T>` still assigns to the contract).
|
|
51
|
+
|
|
52
|
+
### Added
|
|
53
|
+
|
|
54
|
+
- **House scrollbar tokens + `overflow-anchor` — scroll-system Slice C.** The
|
|
55
|
+
shell content scroll box and every `[foldScrollRegion]` now paint one tokenised
|
|
56
|
+
scrollbar: `--fold-scrollbar-size` / `-radius` / `-thumb` / `-track`, with the
|
|
57
|
+
thumb derived from the surface's own text so it adapts per theme **and** per
|
|
58
|
+
surface (a chrome rail vs the page). Standard `scrollbar-width`/`scrollbar-color`
|
|
59
|
+
everywhere, plus a `@supports selector(::-webkit-scrollbar)` layer (shipped in
|
|
60
|
+
`tokens.css`) for the thumb radius on Blink/WebKit. Both regions also set
|
|
61
|
+
`overflow-anchor: auto` so the reading position survives content reflowing above
|
|
62
|
+
them. (The `--fold-scrollbar-*` knobs are component vars, out of the colour
|
|
63
|
+
catalogue — retune them on any ancestor. The viewport-**resize** anchoring
|
|
64
|
+
correction, which native `overflow-anchor` doesn't cover, is a deferred
|
|
65
|
+
follow-up — see `docs/scroll.md`.)
|
|
66
|
+
|
|
67
|
+
- **`[foldScrollRegion]` + the shell scroll registry — scroll-system Slice B.**
|
|
68
|
+
The one opt-in of the scroll model (`docs/scroll.md`): with `fold-app-shell`
|
|
69
|
+
owning the page scroll, a layout that needs an independently-scrolling area (a
|
|
70
|
+
split list/detail, a data-table body, a sticky sidebar, a panel body) marks it
|
|
71
|
+
with `[foldScrollRegion]` instead of hand-rolling `overflow`. The directive sets
|
|
72
|
+
the three foot-guns (`overflow`, `min-*: 0`, `overscroll-behavior: contain`) and
|
|
73
|
+
the thin house scrollbar, and takes an axis (`block` default · `inline` · `both`).
|
|
74
|
+
It **registers with the new `ScrollRegionRegistry`**, which the shell also feeds
|
|
75
|
+
with its own content scroll box; the panel host freezes the registry when a
|
|
76
|
+
modal opens, so the page stops scrolling behind the overlay even though the
|
|
77
|
+
scroll owner is an inner box, not `document.body`. Freezing toggles a
|
|
78
|
+
`.fold-scroll-frozen` class (`overflow: hidden !important`, shipped in
|
|
79
|
+
`tokens.css`), never an inline write, so a region's own overflow is never
|
|
80
|
+
clobbered. Registry injection is optional, so the directive is a useful bounded
|
|
81
|
+
scroll box even without a shell. Gallery `/scroll-region`; 19 specs. (Migrating
|
|
82
|
+
the data-table / panel bodies onto it is deferred — they already scroll
|
|
83
|
+
correctly; the win there is registry coordination, not the overflow.)
|
|
84
|
+
|
|
85
|
+
- **`fold-back-link` — the “← Back” affordance for a detail page.** Three modes,
|
|
86
|
+
picked by which input is set: an in-app `routerLink`, a plain `href`, or — with
|
|
87
|
+
neither — a `<button>` that goes **back in history** (`Location.back()`).
|
|
88
|
+
Router-coupled but degradable (the history mode needs no router; `RouterLink`
|
|
89
|
+
only instantiates on a `routerLink`). Gallery `/back-link`; 4 specs.
|
|
90
|
+
|
|
91
|
+
- **`fold-breadcrumb` — a hierarchical link trail.** Data-driven: pass `[items]`
|
|
92
|
+
where each crumb links by an Angular `routerLink` **or** a plain `href`, so it
|
|
93
|
+
works in a router app and degrades to anchors without one (importing it never
|
|
94
|
+
forces `@angular/router` — `RouterLink` only instantiates on a crumb that sets
|
|
95
|
+
`routerLink`). The last item renders as the current page (`aria-current="page"`),
|
|
96
|
+
never a link; it's a `navigation` landmark with decorative chevron separators.
|
|
97
|
+
Gallery `/breadcrumb`; 5 specs.
|
|
98
|
+
|
|
99
|
+
- **Panel bottom sheet (`side: 'bottom'`) + responsive `side: 'auto'`.** The panel
|
|
100
|
+
host gained two docking edges beyond `left`/`right`: `bottom` is a full-width
|
|
101
|
+
sheet whose height is content-driven up to a max (`85dvh`) with the body
|
|
102
|
+
scrolling, slides up, rounded top, and a top **grabber** that taps to dismiss
|
|
103
|
+
(honours `disableClose`). `auto` docks **right on a wide host, bottom on a
|
|
104
|
+
narrow one** — the switch is container-driven via `@container` on the panel
|
|
105
|
+
host's own inline-size (fold's "responsive on its own width", not the viewport),
|
|
106
|
+
so it reacts to the content region, not the screen. All the modal machinery
|
|
107
|
+
(focus-trap, `inert` barrier, scroll-lock, `disableClose`) is edge-agnostic and
|
|
108
|
+
reused as-is. Motivated by the LaFolieDouce storefront cart on mobile. Gallery
|
|
109
|
+
`/panel` gained “Bottom sheet” + “Auto (by width)” triggers; +6 host specs.
|
|
110
|
+
(Pointer-drag-to-dismiss on the grabber is a deferred nice-to-have.)
|
|
111
|
+
|
|
112
|
+
- **`fold-panel-footer` — the action bar for panels/dialogs.** Pairs with
|
|
113
|
+
`fold-panel-header`: the tokenised bar at a panel's bottom edge (glass top
|
|
114
|
+
border + padding + button alignment), so a panel no longer hand-rolls a
|
|
115
|
+
`<footer class="foot">`. `align="end"` (default — the Annuler/Confirmer pair)
|
|
116
|
+
· `between` (a leading total + trailing actions) · `start`. Sits with
|
|
117
|
+
`flex: none`, so it stays pinned while the body scrolls — no `position: sticky`.
|
|
118
|
+
Probed against the 2nd consumer (LaFolieDouce): **17** hand-rolled panel footers
|
|
119
|
+
across its 3 apps, so it earns a primitive. Gallery `/panel` “Panel footer”; 3 specs.
|
|
120
|
+
|
|
121
|
+
- **`fold-danger-zone` — the destructive-action block.** A framed region for
|
|
122
|
+
“delete X” settings: a title, a projected explanation, and a guarded action.
|
|
123
|
+
**Two appearances** — `filled` (alert-tinted block) and `section`, a danger
|
|
124
|
+
_section_ with a **normal-background body** so it can host ordinary content, only
|
|
125
|
+
the frame + heading signalling danger (the GitHub “Danger Zone” look). The
|
|
126
|
+
destructive control's confirm **reveals on click**: an `actionLabel` button
|
|
127
|
+
opens an in-place `fold-inline-confirm` — a plain “are you sure?”, or a
|
|
128
|
+
type-to-confirm field when `confirmPhrase` is set (the input is never shown until
|
|
129
|
+
the button is clicked). `(confirmed)` emits the typed text (or `""`); omit
|
|
130
|
+
`actionLabel` for a framed section with no action. `role="group"` +
|
|
131
|
+
`aria-labelledby`. Gallery `/danger-zone`; 5 specs.
|
|
132
|
+
|
|
133
|
+
- **`fold-multiselect` bulk actions — `allowSelectAll` / `allowClear`.** A sticky
|
|
134
|
+
bar at the top of the panel offers **Select all** (adds every enabled option,
|
|
135
|
+
skipping disabled rows and preserving an already-picked disabled one) and
|
|
136
|
+
**Clear** (empties the set). Each button is gated — select-all disables once
|
|
137
|
+
everything enabled is picked, clear disables while empty. Labels are overridable
|
|
138
|
+
(`selectAllLabel` / `clearLabel`). The panel is now a wrapper around the
|
|
139
|
+
`role="listbox"` (the bar sits outside it, so it stays valid ARIA). Gallery
|
|
140
|
+
`/listbox` multiselect tab enables both; 5 specs.
|
|
141
|
+
|
|
142
|
+
- **`fold-optgroup` — labelled option groups for the styleable selects.** The
|
|
143
|
+
counterpart to the native `<optgroup>`: wrap `<fold-option>`s in
|
|
144
|
+
`<fold-optgroup label="…">` to sort a long list into sections. Purely
|
|
145
|
+
presentational — the owning `fold-listbox` / `fold-multiselect` now discovers
|
|
146
|
+
options with a `descendants: true` query, so grouped options join the same
|
|
147
|
+
flat, document-ordered list the roving keyboard core walks; the header carries
|
|
148
|
+
`role="group"` + `aria-labelledby` (no `role="option"`), so nav skips straight
|
|
149
|
+
over it. Also supported in the data-driven **`[options]` array API**: an entry
|
|
150
|
+
is a `FoldSelectOption<T>` or a labelled `FoldSelectOptionGroup<T>` (mix both),
|
|
151
|
+
narrowed by the exported `isFoldSelectOptionGroup` guard. Gallery `/listbox`
|
|
152
|
+
“grouped” tab shows both forms; specs cover projected + array discovery,
|
|
153
|
+
cross-group roving and selection.
|
|
154
|
+
|
|
155
|
+
- **The icon devtool browses by category.** `FoldIconDevtoolComponent` now
|
|
156
|
+
groups the live registry into **collapsible sections** — UI · Navigation ·
|
|
157
|
+
Commerce · Music · Status · People · Brands (+ a **Custom** bucket for
|
|
158
|
+
host-registered icons) — each with its icon count; a search only surfaces the
|
|
159
|
+
categories it hits. New public `FOLD_BUILTIN_ICON_CATEGORIES` (+ `FoldIconCategoryId`)
|
|
160
|
+
is the single source of truth for the grouping (each icon file owns its slice).
|
|
161
|
+
|
|
162
|
+
- **`fold-ng/devtools` now builds as a real ng-packagr secondary entry**, so
|
|
163
|
+
`import("fold-ng/devtools")` resolves for **published (npm) consumers**, not
|
|
164
|
+
only source-consumed ones. It compiles to its own FESM + `d.ts` and imports the
|
|
165
|
+
primary `fold-ng` by name; `finalize-dist` normalises ng-packagr's flattened
|
|
166
|
+
`./src/devtools` export to the public `./devtools` subpath (+ a node10 directory
|
|
167
|
+
manifest). `attw` all-🟢, `publint` clean. The api-surface guard was generalised
|
|
168
|
+
to snapshot every published entry point (`.` + `./devtools`).
|
|
169
|
+
|
|
170
|
+
- **`fold-ng/devtools` — an opt-in dev-tools entry, starting with
|
|
171
|
+
`FoldIconDevtoolComponent`.** A **dev-only** floating panel that browses the live
|
|
172
|
+
`FoldIconRegistry` (built-ins + whatever the host app registered), with a search,
|
|
173
|
+
a preview grid, and a mini playground that builds a `<fold-icon>` snippet and
|
|
174
|
+
copies it. The panel is **draggable** (grab the header) and **minimisable**
|
|
175
|
+
(collapses to a pill). Published from a **separate entry** so it never lands in a
|
|
176
|
+
bundle that doesn't ask for it — import it behind a dev guard
|
|
177
|
+
(`if (isDevMode())` + a dynamic `import("fold-ng/devtools")`) so production
|
|
178
|
+
tree-shakes it away. Dogfooded in the gallery (primary-rail "Dev tools" + the
|
|
179
|
+
`/icons` hero CTA).
|
|
180
|
+
- **`FoldIconRegistry.names()`** — the sorted list of every registered icon name
|
|
181
|
+
(built-ins + runtime additions), reactive. Powers catalogue tooling (the icon
|
|
182
|
+
devtool).
|
|
183
|
+
|
|
184
|
+
- **A `commerce` icon category — 21 e-commerce glyphs.** The built-in set had no
|
|
185
|
+
cart, catalogue, payment or fulfilment icons, so a consumer reused `package` as a
|
|
186
|
+
placeholder for both a cart and a delivery tab (`docs/consumer-friction.md` Round
|
|
187
|
+
4 #3). New `COMMERCE_ICONS` (the 7th category, wired into `FOLD_BUILTIN_ICONS` and
|
|
188
|
+
the `/icons` gallery): `shopping-cart` · `shopping-bag` · `basket` · `package` ·
|
|
189
|
+
`package-check` · `tag` · `tags` · `barcode` · `qr-code` · `gift` · `credit-card`
|
|
190
|
+
· `wallet` · `receipt` · `coins` · `banknote` · `percent` · `truck` · `store` ·
|
|
191
|
+
`warehouse` · `map-pin` · `package-return`. Same self-contained inlined-SVG /
|
|
192
|
+
`currentColor` contract; names autocomplete on `FoldIconName`.
|
|
193
|
+
|
|
194
|
+
- **`fold-textarea` — the multiline sibling of `fold-input`.** Same box chrome
|
|
195
|
+
(tokens, sizes, `panel` variant, focus/disabled) via `input-shell.scss` and the
|
|
196
|
+
same label / required / hint / error chrome via `fold-input-base` — so a note
|
|
197
|
+
field is no longer a hand-rolled native `<textarea>` + copied box CSS. **No
|
|
198
|
+
resize handle by design**: the box keeps its `rows` height and **wraps +
|
|
199
|
+
scrolls** overflow (`resize: none; overflow-y: auto`), so a user-dragged corner
|
|
200
|
+
can't break a panel layout. `FormValueControl<string>` (`[formField]` or
|
|
201
|
+
`[(value)]`).
|
|
202
|
+
- **`fold-date` + `fold-time` — the temporal-field wrappers.** Two sibling
|
|
203
|
+
controls (distinct selectors for call-site clarity, the same "one control, one
|
|
204
|
+
job" split as `fold-input` vs `fold-number-input`): `fold-date` wraps the native
|
|
205
|
+
`<input type="date">` family (`type`: `date` · `datetime-local` · `month` ·
|
|
206
|
+
`week`), `fold-time` wraps `<input type="time">`. Both wrap the native control
|
|
207
|
+
the way `fold-select` wraps `<select>` — keeping the OS calendar/clock + mobile
|
|
208
|
+
keyboard — and hand back a **typed `[(value)]`** (the native string, `YYYY-MM-DD`
|
|
209
|
+
/ `HH:mm`), so consumers stop hand-writing an `inputValue($event)` reader.
|
|
210
|
+
`min` / `max` / `step` pass through; both share the `fold-input` box + field
|
|
211
|
+
chrome. **Not** a calendar popover (that's the `fold-calendar` family) — the plain
|
|
212
|
+
fields. `FormValueControl<string>`.
|
|
213
|
+
- Surfaced by the 2nd consumer (LaFolieDouce B2B); see `docs/consumer-friction.md`
|
|
214
|
+
Round 4 #2. The shared `_field-box.scss` `size()` mixin gained a `$height: false`
|
|
215
|
+
opt-out (a `<textarea>`'s height is content-driven), and `readInputValue` now
|
|
216
|
+
reads `<textarea>` targets.
|
|
217
|
+
|
|
218
|
+
## [0.8.1] - 2026-08-02
|
|
219
|
+
|
|
220
|
+
### Changed
|
|
221
|
+
|
|
222
|
+
- **The visual-snapshot tier is no longer a release gate.** Pixel screenshots
|
|
223
|
+
against a live dev server drift for reasons that are not a regression — a
|
|
224
|
+
scrollbar toggling or a font hint moves the frame ~20px — and the baselines
|
|
225
|
+
are per-platform, so gating on them turned the release red on Linux CI _and_
|
|
226
|
+
intermittently on the Mac it was cut from. The shots are now tagged `@visual`
|
|
227
|
+
and run only via `pnpm test:e2e:visual` (a local eyeball aid); the gate
|
|
228
|
+
(`test:e2e`, used by CI and `pnpm release`) excludes them. The geometry that
|
|
229
|
+
matters stays gated, and stably, by measurement in `calendar.spec.ts`. `0.8.0`
|
|
230
|
+
was tagged but never published because of this — `0.8.1` is the first published
|
|
231
|
+
cut of the 0.8 line, and carries the whole calendar family.
|
|
232
|
+
|
|
233
|
+
## [0.8.0] - 2026-08-02
|
|
234
|
+
|
|
235
|
+
### Added
|
|
236
|
+
|
|
237
|
+
- **A calendar family, starting with `fold-calendar-month` and the plain-date
|
|
238
|
+
model it stands on.** The grid is a date axis: seven columns of whole days,
|
|
239
|
+
and over them a layer of **bands**, each stretching from the column its event
|
|
240
|
+
starts on to the column it ends on. A span crossing a week is drawn once per
|
|
241
|
+
week with an open edge on the side that continues, so a three-week holiday
|
|
242
|
+
reads as one thing rather than twenty-one chips. Bands pack into lanes
|
|
243
|
+
(earliest start, then longest) under a `maxLanes` budget, and whatever will
|
|
244
|
+
not fit becomes an overflow chip **in the column of each day that lost
|
|
245
|
+
something** — a hidden span counts against every day it would have covered,
|
|
246
|
+
so the chip says _which_ day to open rather than only that the week is
|
|
247
|
+
crowded, and `overflowClick` emits that day. Events sharing a `groupId`
|
|
248
|
+
collapse into one chip spanning the union of their ranges and carrying the
|
|
249
|
+
count; a half-day edge is kept only on the segment holding the event's real
|
|
250
|
+
edge. Inputs cover `weekStartsOn` (any anchor, not just Monday), `fixedWeeks`,
|
|
251
|
+
`locale` and `labels`; `month` is a two-way `model`, so keyboard paging writes
|
|
252
|
+
back. Project an `<ng-template foldCalendarEvent>` to replace the built-in
|
|
253
|
+
chip. Generic over `T`, so an event's `data` survives the round trip to
|
|
254
|
+
`eventClick` without a cast.
|
|
255
|
+
- **`fold-calendar-agenda` — a rail of what is still ahead, grouped by day.**
|
|
256
|
+
The counterpart to the grids: they answer "what does this month look like",
|
|
257
|
+
it answers "what do I do next". Its `todo` slice keeps only the events asking
|
|
258
|
+
for attention — the `warning` and `alert` tones, the **same scale the chips
|
|
259
|
+
paint with**, so the rail needs no second notion of urgency — and carries the
|
|
260
|
+
count as a badge. An event already running is filed under the boundary rather
|
|
261
|
+
than its real start, so a three-week absence that began last week sits at the
|
|
262
|
+
top of what's next instead of in a past day the rail never shows. Days inside
|
|
263
|
+
the next week are named relatively ("Today", "Tomorrow", then the weekday),
|
|
264
|
+
which reads faster than a date at that distance. `mode` and `collapsed` are
|
|
265
|
+
two-way `model`s — persist the collapse if you want it to stick; the package
|
|
266
|
+
stores nothing. Pure `foldBuildAgenda` / `foldCountActionable` behind it.
|
|
267
|
+
- **`fold-calendar-source-filter` — chips that switch each feed of a merged
|
|
268
|
+
calendar on and off.** A calendar usually merges several feeds (a programme,
|
|
269
|
+
staff leave, contracts); an event names its own with `sourceKey`, a
|
|
270
|
+
`FoldCalendarSource` declares the label and dot, and the chips count what each
|
|
271
|
+
contributes. They own the **selection only** — the caller runs the pure
|
|
272
|
+
`foldFilterBySource()` over its own events, so the chips never learn how
|
|
273
|
+
anything is fetched. An event with no `sourceKey` belongs to no feed and no
|
|
274
|
+
chip can hide it. Each chip is a real toggle (`aria-pressed` in both states)
|
|
275
|
+
whose accessible name says which feed and whether it is showing, because the
|
|
276
|
+
tick and the dot are colour and colour cannot be the only carrier.
|
|
277
|
+
- **`fold-calendar-toolbar` — the chrome that makes the views one calendar.**
|
|
278
|
+
Jump to today, page back and forward, the period's name, and the view switch.
|
|
279
|
+
It owns no data: both pieces of state are two-way `model`s, so a page binds
|
|
280
|
+
the same `date` and `view` it hands the view on screen and paging works with
|
|
281
|
+
no output handler. The step matches the reading — a month under the month
|
|
282
|
+
view, a week under the week view, a day under the day view. The title carries
|
|
283
|
+
`aria-live="polite"`, so paging announces where a keyboard user landed instead
|
|
284
|
+
of changing silently. Backed by pure `foldShiftDate`, `foldRangeForView` (the
|
|
285
|
+
window a caller fetches — the whole painted grid for a month, not just its
|
|
286
|
+
days) and `foldViewTitle`.
|
|
287
|
+
- **The three column views that complete the drill-down: `fold-calendar-week`,
|
|
288
|
+
`fold-calendar-day` and `fold-calendar-list`.** Where the month grid packs
|
|
289
|
+
spans into lanes, these simply list what covers each day — so nothing is
|
|
290
|
+
clipped and no lane budget can hide anything. `week` is seven day columns;
|
|
291
|
+
`day` is one day in full, with a tag-qualified `button[empty]` slot for a "new request" action
|
|
292
|
+
when nothing sits on it; `list` is the flat chronological reading, each row
|
|
293
|
+
led by its span (formatted with `Intl`'s own range formatter, which collapses
|
|
294
|
+
a single day to one date and factors out a shared month). All three take the
|
|
295
|
+
same `foldCalendarEvent` template, labels and `locale` as the month view.
|
|
296
|
+
Their a11y is **simpler than the month's on purpose**: because a chip nests
|
|
297
|
+
inside the day it belongs to rather than spanning columns, every control is a
|
|
298
|
+
real child in the natural tab order — no roving tabindex, no `aria-hidden`,
|
|
299
|
+
every event reachable by keyboard. New pure builders `foldBuildWeek` and
|
|
300
|
+
`foldBuildDay` back them.
|
|
301
|
+
- **`FoldCalendarDate` — the package's date primitive is a plain
|
|
302
|
+
`YYYY-MM-DD` string, not a `Date`.** A calendar of all-day spans deals in
|
|
303
|
+
dates, not instants: `new Date("2026-05-18")` is UTC midnight, i.e. the 17th
|
|
304
|
+
anywhere west of Greenwich, and that off-by-one is the most common calendar
|
|
305
|
+
bug there is. Strings remove the class by construction, compare
|
|
306
|
+
lexicographically (`a <= b` _is_ "on or before"), are `===`-equal when they
|
|
307
|
+
mean the same day, and are already the wire format. Arithmetic runs through
|
|
308
|
+
`Date.UTC`, so no DST boundary can repeat or skip a day. Ships with
|
|
309
|
+
`foldToday`, `foldAddDays`, `foldAddMonths`, `foldStartOfWeek`,
|
|
310
|
+
`foldStartOfMonth`, `foldEndOfMonth`, `foldDaysBetween`, `foldWeekdayIndex`,
|
|
311
|
+
`foldWeekdayOf`, `foldIsWeekend`, `foldIsCalendarDate`, `foldToNativeDate` and
|
|
312
|
+
`foldFromNativeDate` (the inbound bridge — the conversion every consumer would
|
|
313
|
+
otherwise write as `toISOString().slice(0, 10)`, which is the very timezone bug
|
|
314
|
+
this design exists to remove), plus the layout entry points
|
|
315
|
+
`foldBuildMonthGrid`, `foldBuildWeek`, `foldBuildDay`, `foldEventsOnDay`,
|
|
316
|
+
`foldEventsInRange` and `foldFilterBySource`. Supported range is
|
|
317
|
+
`0001-01-01`–`9999-12-31`: the four-digit year is what makes the lexicographic
|
|
318
|
+
guarantee hold, so it is enforced rather than assumed.
|
|
319
|
+
`fold-timeline` keeps a native `Date` on purpose — it plots dated _instants_,
|
|
320
|
+
which is the other domain.
|
|
321
|
+
- **Month and weekday names come from `Intl`, not from a label token.** The
|
|
322
|
+
`locale` input drives `Intl.DateTimeFormat`, so every locale works without
|
|
323
|
+
hand-translating twelve month names; `FoldCalendarLabels` (with
|
|
324
|
+
`provideFoldCalendarLabels`) covers only what `Intl` cannot supply — the today
|
|
325
|
+
marker, the overflow chip and the event count.
|
|
326
|
+
|
|
327
|
+
- **Everything the calendar draws _around_ an event is a projectable template.**
|
|
328
|
+
The chip was replaceable from day one; the containers were not, which had it
|
|
329
|
+
backwards — an app could restyle the smallest unit and not the cell behind it.
|
|
330
|
+
Four more `<ng-template>` seams, each with a typed context and an
|
|
331
|
+
`ngTemplateContextGuard` (so `let-` variables are real types under
|
|
332
|
+
`strictTemplates`, not `any`): **`foldCalendarDay`** replaces the inside of a
|
|
333
|
+
month cell — the hook for a public holiday, a closure, "3/8 staffed";
|
|
334
|
+
**`foldCalendarHeading`** replaces the agenda's day heading;
|
|
335
|
+
**`foldCalendarTitle`** replaces the toolbar's `<h2>`, which also lets a page
|
|
336
|
+
give the title the heading level its outline needs; **`foldCalendarOverflow`**
|
|
337
|
+
replaces the `+N` chip. Alongside them, `dayModifiers: (day) => string[]`
|
|
338
|
+
emits an app's own names as one `data-fold-day-modifiers` attribute, matchable
|
|
339
|
+
with `[data-fold-day-modifiers~="holiday"]` — so nobody has to write CSS
|
|
340
|
+
against an internal class name. `foldCalendarEvent` is now generic too, so
|
|
341
|
+
`event.data` comes back as the app's own record.
|
|
342
|
+
- **`fold-calendar-timegrid` — the reading the other four cannot give: when
|
|
343
|
+
_inside_ a day.** Hour columns for a week or a single day (`dayCount`), with
|
|
344
|
+
the all-day strip on top. A meeting is a block whose height is its duration
|
|
345
|
+
and whose width is shared with whatever it collides with; an absence is a band
|
|
346
|
+
across the strip, spanning days through **the same packer the month grid
|
|
347
|
+
uses** — extracted rather than copied, so a three-day leave request reads
|
|
348
|
+
identically in both.
|
|
349
|
+
|
|
350
|
+
Time is modelled as wall-clock `HH:mm` (`FoldCalendarTime`), **not an
|
|
351
|
+
instant** — the same decision as the date, for the same reason: 09:00 is the
|
|
352
|
+
hour on the wall, and an instant re-derives that from a zone every render,
|
|
353
|
+
which is one wrong default away from drawing the wrong hour. The app converts
|
|
354
|
+
once at its own boundary.
|
|
355
|
+
|
|
356
|
+
Two details that are the difference between a real time grid and a demo: the
|
|
357
|
+
overlap test is **exclusive** at the boundary, so back-to-back meetings keep
|
|
358
|
+
the full width instead of each taking half; and events are grouped into
|
|
359
|
+
**clusters**, so one triple-booked morning does not narrow an unrelated
|
|
360
|
+
afternoon. A span crossing midnight becomes one block per day, each with the
|
|
361
|
+
right open edge, rather than one impossible block running off the bottom.
|
|
362
|
+
|
|
363
|
+
`now` is an input, never a clock the package reads — `today` already works
|
|
364
|
+
that way, and a server render that invented one would hydrate to a different
|
|
365
|
+
position. Positions are **fractions of the visible window**, so `dayStart` /
|
|
366
|
+
`dayEnd` and the CSS height stay independent. Backed by pure
|
|
367
|
+
`foldBuildTimeGrid` and `foldLayOutOverlaps`.
|
|
368
|
+
|
|
369
|
+
- **`foldFromTemporal` — the family is Temporal-native without depending on
|
|
370
|
+
it.** `Temporal.PlainDate.toString()` **is** `YYYY-MM-DD`: the primitive
|
|
371
|
+
chosen for correctness turns out to be exactly Temporal-shaped, which no
|
|
372
|
+
`Date`-based calendar can say. The bridge accepts a `PlainDate`,
|
|
373
|
+
`PlainDateTime` or `ZonedDateTime` — typed structurally, so it compiles and
|
|
374
|
+
runs on an engine that has none of them — and drops the time, which is what a
|
|
375
|
+
whole-day calendar wants **and** keeps the day the value itself means rather
|
|
376
|
+
than the one UTC would have picked. The other direction needs no helper:
|
|
377
|
+
`Temporal.PlainDate.from(foldDate)` already accepts one of ours.
|
|
378
|
+
- **The week now comes from the locale, not from a guess.** `Intl.Locale`
|
|
379
|
+
knows which day a locale's week opens on and which days it rests — most
|
|
380
|
+
calendars stop at the month names and hard-code Monday, which is right in
|
|
381
|
+
Paris and wrong in Chicago, Cairo and Malé. `weekStartsOn` and `weekendDays`
|
|
382
|
+
default to `foldLocaleWeekInfo(locale)` and stay overridable, so the common
|
|
383
|
+
case needs no input at all. Both `getWeekInfo()` and the older `weekInfo`
|
|
384
|
+
getter are probed, and a runtime with neither falls back to Monday + Sat/Sun.
|
|
385
|
+
- **ISO week numbers, as an optional leading column.** `showWeekNumbers` on
|
|
386
|
+
`fold-calendar-month`, backed by `foldIsoWeek` / `foldIsoWeekYear`. ISO weeks
|
|
387
|
+
start on Monday and belong to the year holding their **Thursday**, so they are
|
|
388
|
+
deliberately _not_ derived from the calendar's own anchor — 1 January is
|
|
389
|
+
sometimes week 53 of the year before, which is the whole point of the rule and
|
|
390
|
+
the reason European B2B reporting asks for it. The column is a real grid
|
|
391
|
+
track, so every placed element — cells, bands, overflow chips — shifts with it.
|
|
392
|
+
- **A `formats` input beside `labels`.** Labels let a locale translate; this
|
|
393
|
+
lets it **reformat**. Every `Intl` option bag the family uses lives in one
|
|
394
|
+
table (`FOLD_CALENDAR_FORMATS`, now exported); `formats` merges over it per
|
|
395
|
+
instance — a narrow weekday header, a numeric month, a four-digit year.
|
|
396
|
+
- **A print stylesheet for the month.** Browsers drop backgrounds but keep text
|
|
397
|
+
colour, so a dark theme printed as-is is light-on-white — unreadable. The
|
|
398
|
+
print block re-expresses everything in system colours (`Canvas`/`CanvasText`/
|
|
399
|
+
`GrayText`), which are neither theme tokens nor hard-coded values, and asks
|
|
400
|
+
for ink on exactly one thing: the tone bar, the last cue telling two bands
|
|
401
|
+
apart on paper.
|
|
402
|
+
- **The month layout is ~5× faster, and the benchmark ships with it.** The cost
|
|
403
|
+
was never the algorithm — it was the representation: every comparison in the
|
|
404
|
+
candidate sort called `foldDaysBetween`, which re-parsed two strings and built
|
|
405
|
+
two `Date`s, and the whole feed was re-filtered once per week row. Spans now
|
|
406
|
+
carry epoch-day bounds computed once, rows are bucketed in a single pass
|
|
407
|
+
(`O(rows × N)` → `O(N)`), and clipping is integer arithmetic. The public model
|
|
408
|
+
is unchanged — this is internal only. Measured on a month layout, mean of 20:
|
|
409
|
+
1 000 events 4.8 ms → **0.99 ms**, 5 000 28.9 → **5.0**, 20 000 124 → **22**.
|
|
410
|
+
`pnpm bench:calendar` re-runs it against a committed budget and exits non-zero
|
|
411
|
+
when a size blows it.
|
|
412
|
+
- **`foldCalendarNextFocus` is public — the geometry tier is now complete.**
|
|
413
|
+
The pure builders (`foldBuildMonthGrid`, `foldBuildWeek`, `foldBuildDay`,
|
|
414
|
+
`foldBuildAgenda`) and the period functions (`foldShiftDate`,
|
|
415
|
+
`foldRangeForView`, `foldViewTitle`) let an app lay a calendar out and draw it
|
|
416
|
+
its own way; the arrow-key map was the one piece missing, and a hand-rolled
|
|
417
|
+
date grid owes its users the same keyboard as the built-in one. Documented as
|
|
418
|
+
a **tier** in the README rather than left as an accident of what happened to
|
|
419
|
+
be exported. `foldFocusDayCell` deliberately stays internal: it reads a
|
|
420
|
+
`data-fold-day` attribute this package writes, and exporting it would freeze
|
|
421
|
+
that attribute into the public contract.
|
|
422
|
+
- **The view switch is open.** `FoldCalendarView` keeps the four built-ins as
|
|
423
|
+
autocompleting literals but accepts any string, and `views` takes
|
|
424
|
+
`{ value, label }` — so an app's own reading (a resource grid, a timeline) can
|
|
425
|
+
sit in the same toolbar without the library knowing about it. Paging and
|
|
426
|
+
titling an unrecognised view fall back to month semantics, which always lands
|
|
427
|
+
on a real date.
|
|
428
|
+
- **A `FoldCalendarDay` now carries its own `eventCount` and `hiddenCount`.**
|
|
429
|
+
Both were previously recomputed per cell per change-detection cycle (an O(N)
|
|
430
|
+
filter, with an allocation, 35 times a tick) or exposed as a positional
|
|
431
|
+
`hiddenByDay` array only readable when crossed with the row's dates. Counting
|
|
432
|
+
once while the grid is built is both cheaper and self-describing, and it is
|
|
433
|
+
what lets a day cell announce "5 events, 2 not shown".
|
|
434
|
+
|
|
435
|
+
### Changed
|
|
436
|
+
|
|
437
|
+
- **A standalone `fold-view-nav` now separates itself from the content it heads
|
|
438
|
+
by the same gap a `fold-nav-layout` applies.** A horizontal bar used on its own
|
|
439
|
+
(outside a layout) gained a `margin-block-end` equal to `--fold-nav-layout-gap`
|
|
440
|
+
(default `--fold-space-lg`, 16px) — the value is now shared through a
|
|
441
|
+
`nav-content-gap` mixin (`layout/_nav-gap.scss`) that both the layout and the
|
|
442
|
+
bar `@use`, so the two can't drift and consumers stop hand-rolling a margin.
|
|
443
|
+
Applied only to a **standalone horizontal** bar: inside a `fold-nav-layout` the
|
|
444
|
+
layout still owns the gap (no double space), and a vertical bar is a side rail,
|
|
445
|
+
not a header. `fold-tabs` is unaffected (an in-place widget, not a bar that
|
|
446
|
+
introduces following content). Purely additive spacing — the layout's own
|
|
447
|
+
rendering is unchanged.
|
|
448
|
+
|
|
449
|
+
- **The weekend is its own input, because it is its own fact.**
|
|
450
|
+
`foldIsWeekEnd(date, weekStartsOn)` defined the weekend as "the last two
|
|
451
|
+
columns", which shades **Friday and Saturday** on a Sunday-first calendar and
|
|
452
|
+
calls Sunday a working day. Replaced by `foldIsWeekend(date, weekendDays)`
|
|
453
|
+
with a `weekendDays` input (default `['sat', 'sun']`) on the month and week
|
|
454
|
+
views — the anchor moves the columns, it does not move the days people rest
|
|
455
|
+
on, and a Saturday-first calendar resting Fri+Sat is now expressible.
|
|
456
|
+
- **`foldRangeForView('list')` returns the month, not the painted grid.** It
|
|
457
|
+
shared the month branch, so a May list showed late-April events: a month view
|
|
458
|
+
paints padding days and needs their events, a list has none.
|
|
459
|
+
- **A projected template replaces a list row's whole inside**, as it already did
|
|
460
|
+
in the other four views. The bar and the date used to render outside the
|
|
461
|
+
branch, so the same template rendered differently depending on which view
|
|
462
|
+
hosted it, and a one-element template landed in a four-column grid.
|
|
463
|
+
- **The month day cell is a `gridcell`, not a `<button>` wearing the role.**
|
|
464
|
+
`role="gridcell"` on a button replaces its native role, so activation is now
|
|
465
|
+
wired explicitly (`Enter`/`Space`) and the cell is the focusable widget the
|
|
466
|
+
ARIA grid pattern asks for. `role="grid"` and its accessible name moved onto
|
|
467
|
+
the host, which also removes the second root the keyboard helper had to find.
|
|
468
|
+
- **`fold-calendar-week`'s `date` is a plain input.** It was a
|
|
469
|
+
`model.required` that the component never wrote to — a two-way binding
|
|
470
|
+
promising something it does not do. Pair it with the toolbar, which does own
|
|
471
|
+
the paging.
|
|
472
|
+
- **The five views share one host directive for their chrome.** Labels, locale
|
|
473
|
+
and the projected chip template were eight identical lines copied five times,
|
|
474
|
+
and the `Intl.DateTimeFormat` option bags were duplicated with small
|
|
475
|
+
divergences — which is how the day view came to ask for
|
|
476
|
+
`{ weekday: 'long', month: 'long' }` and print "Saturday May", a phrase in no
|
|
477
|
+
locale. There is now one table of formats (`FOLD_CALENDAR_FORMATS`) and one
|
|
478
|
+
cache keyed by locale.
|
|
479
|
+
- **Tones are written once.** The four roles were reimplemented in three places
|
|
480
|
+
(the month's bands, the shared chip, the list's rows, which had drifted into
|
|
481
|
+
its own copy); they are now one mixin parameterised by selector.
|
|
482
|
+
- **RTL decorations follow the reading direction.** Continuation edges, the
|
|
483
|
+
chevrons, the cell separators and the chip padding were physical properties,
|
|
484
|
+
so a right-to-left calendar squared off the wrong side. All logical now.
|
|
485
|
+
- **The label token drops `dateRange`** (declared, translated by consumers,
|
|
486
|
+
used nowhere — `Intl.formatRange` already orders a span per locale) and gains
|
|
487
|
+
`hiddenCount`, `agendaModes` and `agendaMore`.
|
|
488
|
+
|
|
489
|
+
### Fixed
|
|
490
|
+
|
|
491
|
+
- **Two events sharing an `id` no longer merge into one.** The layout keyed its
|
|
492
|
+
identity map on the id alone, so a duplicate was indistinguishable from an
|
|
493
|
+
explicit `groupId`: the second event was **never rendered**, both bands showed
|
|
494
|
+
the first one's label, a bogus group counter appeared, and the four column
|
|
495
|
+
views threw `NG0955` on their `track event.id`. Duplicates now stay separate,
|
|
496
|
+
with a dev-mode warning naming the id.
|
|
497
|
+
- **`maxLanes` is clamped in one place, and coerced from an attribute.** The
|
|
498
|
+
invariant lived in two and only one enforced it: `maxLanes="2"` as a string
|
|
499
|
+
built an overflow row of `"22"`, a negative value made `repeat(-5, …)` and the
|
|
500
|
+
CSS parser dropped the whole `grid-template-rows` rule, and `NaN` **disabled
|
|
501
|
+
the lane budget entirely** (`lane >= NaN` is never true) so nothing was ever
|
|
502
|
+
counted as hidden.
|
|
503
|
+
- **A reversed range (`end < start`) is put back in order.** Left alone it
|
|
504
|
+
rendered _twice, wrongly_: `grid-column: 5 / 2`, which CSS Grid silently
|
|
505
|
+
swaps, so a band covered four days that every other view — asking
|
|
506
|
+
`start <= day && end >= day` — reported as empty. Same input, two
|
|
507
|
+
contradictory renders, no error.
|
|
508
|
+
- **A month that is not a date yields no grid, and says so.** `foldIsCalendarDate`
|
|
509
|
+
existed, was tested, and was called by nothing: `month="not-a-date"` produced
|
|
510
|
+
a `role="grid"` with headers and zero rows, and `month="2026-13-45"` produced
|
|
511
|
+
a December calendar displayed with total confidence.
|
|
512
|
+
- **A collapsed group shows its most severe member.** It kept the first event in
|
|
513
|
+
document order and merged only the dates, so a cancelled first member greyed
|
|
514
|
+
out a whole group containing an alert, and an open-ended member inside a
|
|
515
|
+
closed group had its open edge dropped — a contract with no end drawn as
|
|
516
|
+
finished. Open edges are now the OR of the members, and the representative is
|
|
517
|
+
the most severe by tone (ties to document order), so tone, icon, label and
|
|
518
|
+
source all come from one event that really exists.
|
|
519
|
+
- **The agenda's `limit` can no longer empty the rail.** `limit: 0` printed
|
|
520
|
+
"Nothing to handle — all up to date." directly under a badge saying otherwise,
|
|
521
|
+
and `NaN` did the same (`slice(0, NaN)` returns nothing). It now clamps to at
|
|
522
|
+
least one day and reports what it cut off, so "there is more" stops rendering
|
|
523
|
+
as "there is nothing".
|
|
524
|
+
- **Paging with the keyboard twice in a row keeps the focus.** The deferred
|
|
525
|
+
focus target was a signal that was never reset, so repeating the _same_
|
|
526
|
+
transition wrote the same value, the effect did not re-run, and focus fell
|
|
527
|
+
onto `<body>`. The request is now consumed, and applied in an
|
|
528
|
+
`afterNextRender` rather than an `effect` — the ordering of an effect against
|
|
529
|
+
the DOM it wants to touch is not contracted, and has already changed between
|
|
530
|
+
Angular versions.
|
|
531
|
+
- **The day view's `[empty]` slot no longer swallows a child.** `empty` is also
|
|
532
|
+
an input on `fold-data-table` and `fold-field`; an unqualified selector
|
|
533
|
+
captured either of them, and with no default slot the child simply vanished
|
|
534
|
+
(rule 4.8). Tag-qualified, with a default slot behind it and a projection test.
|
|
535
|
+
- **Years below 100 no longer jump 1900 years.** `Date.UTC(99, …)` means 1999,
|
|
536
|
+
so `foldAddDays("0099-12-31", 1)` returned `"2000-01-01"`. Years are also
|
|
537
|
+
zero-padded to four digits, without which they sort before every other date.
|
|
538
|
+
- **`foldFilterBySource` accepts `null`** — the initial value of the very model
|
|
539
|
+
it exists to consume. Every caller was writing the same ternary; the gallery
|
|
540
|
+
did too.
|
|
541
|
+
- **The band icon takes the tone's colour**, and the collapsed agenda spine
|
|
542
|
+
names the slice it will open into rather than always saying "To handle". The
|
|
543
|
+
agenda's slice switch has its own accessible name instead of repeating the
|
|
544
|
+
rail's, and its badge honours a caller's `isActionable`, so it can no longer
|
|
545
|
+
disagree with the list under it.
|
|
546
|
+
- **The calendar is now honestly Gregorian in every locale.** Names were
|
|
547
|
+
formatted in the locale's _own_ calendar system, so `locale="ar-SA"` printed
|
|
548
|
+
Hijri month names over the Gregorian day numbers the grid counts, on rows that
|
|
549
|
+
break on Gregorian months — a calendar contradicting itself. The `Intl`
|
|
550
|
+
formatters are pinned to `calendar: 'gregory'`, so a locale now localises the
|
|
551
|
+
_language_ of the names, not the calendar behind them. A true non-Gregorian
|
|
552
|
+
layout is a separate widget this family does not pretend to be.
|
|
553
|
+
- **`foldIsCalendarTime` rejects an impossible minute field.** `"10:75"` and
|
|
554
|
+
`"23:60"` are `HH:mm`-shaped and under 1440 minutes, so a range check on the
|
|
555
|
+
total waved them through; the hour and minute fields are now validated
|
|
556
|
+
separately (`00:00`–`23:59`, plus `24:00`), the same rigour the date guard
|
|
557
|
+
applies to `2026-02-30`.
|
|
558
|
+
|
|
11
559
|
## [0.7.0] - 2026-07-29
|
|
12
560
|
|
|
13
561
|
### Added
|
|
@@ -715,7 +1263,10 @@ design-token stylesheet.
|
|
|
715
1263
|
`currentColor`; `prefers-reduced-motion` + `forced-colors` are respected;
|
|
716
1264
|
strings localise via inputs / providers (`provideFoldPanelLabels`).
|
|
717
1265
|
|
|
718
|
-
[unreleased]: https://github.com/hugoheynard/fold-ng/compare/v0.
|
|
1266
|
+
[unreleased]: https://github.com/hugoheynard/fold-ng/compare/v0.9.0...HEAD
|
|
1267
|
+
[0.9.0]: https://github.com/hugoheynard/fold-ng/releases/tag/v0.9.0
|
|
1268
|
+
[0.8.1]: https://github.com/hugoheynard/fold-ng/releases/tag/v0.8.1
|
|
1269
|
+
[0.8.0]: https://github.com/hugoheynard/fold-ng/releases/tag/v0.8.0
|
|
719
1270
|
[0.7.0]: https://github.com/hugoheynard/fold-ng/releases/tag/v0.7.0
|
|
720
1271
|
[0.6.1]: https://github.com/hugoheynard/fold-ng/releases/tag/v0.6.1
|
|
721
1272
|
[0.6.0]: https://github.com/hugoheynard/fold-ng/releases/tag/v0.6.0
|