wiki-formant 0.59.0 → 0.60.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/README.md CHANGED
@@ -60,7 +60,7 @@ const letters = taxonomy.alphaControls('ecosystem', all, state); // [] below the
60
60
  Every control arrives as `{value, label, count, href, active}`, its `href` already built through your one builder with **every other axis carried along** — the arithmetic that, done by hand, drops the reader's letter the first time they press a chip.
61
61
 
62
62
  - **The reset control leads the row**, flagged `reset`, so a consumer that maps the array cannot ship a narrowed view with nothing to press to widen it.
63
- - **`alphaControls` returns `[]` below the index threshold**, folding in `needsAlphaIndex` — one call is either the index you needed or nothing, rather than a decision each caller makes and one caller forgets.
63
+ - **`alphaControls` returns `[]` below the index threshold**, folding in `needsAlphaIndex` — one call is either the index you needed or nothing, rather than a decision each caller makes and one caller forgets. A hub's sort toggle reads the same predicate over the same unfiltered count (`needsAlphaIndex(all.length)`; 40 pages unless `alphaIndexMinPages` says otherwise), so a set small enough to need no index offers no sort either.
64
64
  - **`resolveLetter` drops a letter no page starts with.** A stray `?letter=Q` would otherwise empty the grid with no control marked to explain why.
65
65
 
66
66
 
@@ -367,7 +367,7 @@ are unit-tested without a DOM.
367
367
  `wiki-formant/react` carries `'use client'`, and that is a module-level boundary: anything exported from it hydrates in the consumer's tree whether or not it uses a hook. `wiki-formant/react-server` is the same React, without the directive — for the parts of a wiki that are pure functions of their props and should ship no JavaScript at all.
368
368
 
369
369
  `FacetBar` renders the rows `createTaxonomy` already produces. This is why the taxonomy exports a rows model rather than markup: the rows could always cross the boundary and, until this subpath existed, the markup could not, so all three wikis hand-rendered it and two put `aria-pressed` on an `<a>`. `Breadcrumbs` renders the trail and its `BreadcrumbList` JSON-LD together, because a trail whose structured data is written somewhere else is a trail that will one day disagree with its own markup — which is the case Google penalises. It takes a `base` origin: structured-data URLs must be absolute and a package cannot know the site.
370
- `FacetBar` takes a `count` class to render each count as its own element, and `alphaFirst` to lead with the A–Z row. Its JSON-LD goes out through `JsonLd`, which is exported for every other payload a page emits: it re-encodes each `<` as `\u003c`, because these payloads carry authored titles and an authored `</script>` would otherwise close the tag.
370
+ `FacetBar` takes a `count` class to render each count as its own element, and `alphaFirst` to lead with the A–Z row. A chip row above the grid and a panel beside it are the same markup laid out by different stylesheets, so the class names are what tell them apart: `letterRow`, `letterControl` and `letterControlActive` style the A–Z row apart from the facet rows (each falls back to `row`, `control` or `controlActive`); a `text` class wraps each label in its own element and gives a facet option its value as `title`, so a long value can truncate while its count stays in view; and `letterCounts="title"` keeps each letter a bare glyph with its count in the tooltip. With none of them set the markup is what it was. Its JSON-LD goes out through `JsonLd`, which is exported for every other payload a page emits: it re-encodes each `<` as `\u003c`, because these payloads carry authored titles and an authored `</script>` would otherwise close the tag.
371
371
 
372
372
  `PageNav` is the previous/next pair at the foot of an article — the sequential read the infobox rail's lateral links do not cover. Ordering is the caller's, because it is the one part that is never portable: a wiki's sequence is its section's configured sort, a knowledge base's is a taxonomy walk. Pair it with `adjacentPages` from `wiki-formant/pagination` over a list you already hold — neither wiki needs a query for it, and the two indexed lookups the neighbours used to cost were the reason one of them dropped the control.
373
373
 
@@ -31,15 +31,35 @@ export declare const Anchor: WikiLinkComponent;
31
31
  * tree, which is a shared convention, these are each design system's own words
32
32
  * for a chip and a row. Defaults are given so a consumer that shares them writes
33
33
  * nothing.
34
+ *
35
+ * Whether the bar is a chip row above the grid or a panel beside it is decided
36
+ * here and nowhere else: the markup is the same, the stylesheet behind these
37
+ * names lays it out.
34
38
  */
35
39
  export interface FacetBarClassNames {
36
40
  root?: string;
41
+ /** Each facet's row, and the A–Z row unless `letterRow` is set. */
37
42
  row?: string;
43
+ /** A row's heading: the facet's label, or `alphaLabel`. */
38
44
  label?: string;
45
+ /** A facet option, and a letter unless `letterControl` is set. */
39
46
  control?: string;
40
47
  controlActive?: string;
41
48
  /** Set it and each count is its own `<span>` with this class, not ` (n)` text. */
42
49
  count?: string;
50
+ /**
51
+ * Set it and each control's text is its own `<span>` with this class, and a
52
+ * facet option carries its value as `title`: the hook for truncating a long
53
+ * value while its count stays in view, with the full value one hover away.
54
+ */
55
+ text?: string;
56
+ /**
57
+ * The A–Z row and its letters, which a panel draws as a block of glyphs
58
+ * while its facet options run full width. Each falls back to its shared key.
59
+ */
60
+ letterRow?: string;
61
+ letterControl?: string;
62
+ letterControlActive?: string;
43
63
  }
44
64
  export interface FacetBarProps {
45
65
  link: WikiLinkComponent;
@@ -49,6 +69,11 @@ export interface FacetBarProps {
49
69
  alphaLabel?: string;
50
70
  /** Lead with the A–Z row: two lines against a facet block's twenty. */
51
71
  alphaFirst?: boolean;
72
+ /**
73
+ * Where a letter's count goes. `'inline'` prints it like an option's;
74
+ * `'title'` keeps the letter a bare glyph and puts its count in the tooltip.
75
+ */
76
+ letterCounts?: 'inline' | 'title';
52
77
  classNames?: FacetBarClassNames;
53
78
  /** The landmark's accessible name. */
54
79
  label?: string;
@@ -63,7 +88,7 @@ export interface FacetBarProps {
63
88
  * `aria-current`, not `aria-pressed`: a link is not a toggle button and does not
64
89
  * take that attribute.
65
90
  */
66
- export declare function FacetBar({ link: Link, facets, letters, alphaLabel, alphaFirst, classNames, label: navLabel, }: FacetBarProps): import("react").JSX.Element | null;
91
+ export declare function FacetBar({ link: Link, facets, letters, alphaLabel, alphaFirst, letterCounts, classNames, label: navLabel, }: FacetBarProps): import("react").JSX.Element | null;
67
92
  export interface FacetSummaryProps {
68
93
  shown: number;
69
94
  total: number;
@@ -28,7 +28,8 @@ import { blockLocation } from './revisions.js';
28
28
  * Exported because `block-views` renders links too and had declared the same
29
29
  * three lines; a default that exists twice is a default that can diverge.
30
30
  */
31
- export const Anchor = ({ href, className, children }) => _jsx("a", { href: href, className: className, children: children });
31
+ export const Anchor = (props) => _jsx("a", { ...props });
32
+ const pagesPhrase = (n) => `${n} page${n === 1 ? '' : 's'}`;
32
33
  /**
33
34
  * The section's second axis, as pressable chips.
34
35
  *
@@ -39,20 +40,16 @@ export const Anchor = ({ href, className, children }) => _jsx("a", { href: href,
39
40
  * `aria-current`, not `aria-pressed`: a link is not a toggle button and does not
40
41
  * take that attribute.
41
42
  */
42
- export function FacetBar({ link: Link, facets, letters, alphaLabel = 'A–Z', alphaFirst = false, classNames = {}, label: navLabel = 'Filter pages', }) {
43
+ export function FacetBar({ link: Link, facets, letters, alphaLabel = 'A–Z', alphaFirst = false, letterCounts = 'inline', classNames = {}, label: navLabel = 'Filter pages', }) {
43
44
  if (!facets.length && !letters.length)
44
45
  return null;
45
- const { root = 'stack tight', row = 'cluster', label = 'form-label', control = 'tag', controlActive = 'tag tag-removable', count, } = classNames;
46
- const chip = (c) => (_jsxs(Link, { href: c.href, className: c.active ? controlActive : control, "aria-current": c.active ? 'true' : undefined, ...(c.title ? { title: c.title } : {}), children: [c.text, count ? _jsx("span", { className: count, children: c.n }) : ` (${c.n})`] }, c.key));
47
- const facetRows = facets.map(facet => (_jsxs("div", { className: row, children: [_jsx("span", { className: label, children: facet.label }), facet.options.map(o => chip({ key: o.value, href: o.href, active: o.active, text: o.value, n: o.count }))] }, facet.key)));
46
+ const { root = 'stack tight', row = 'cluster', label = 'form-label', control = 'tag', controlActive = 'tag tag-removable', count, text, letterRow = row, letterControl = control, letterControlActive = controlActive, } = classNames;
47
+ const chip = (c, classes, opts) => (_jsxs(Link, { href: c.href, className: c.active ? classes.active : classes.idle, "aria-current": c.active ? 'true' : undefined, ...(opts.title ? { title: opts.title } : {}), children: [text ? _jsx("span", { className: text, children: c.label }) : c.label, opts.showCount && (count ? _jsx("span", { className: count, children: c.count }) : ` (${c.count})`)] }, c.value || 'all'));
48
+ const facetRows = facets.map(facet => (_jsxs("div", { className: row, children: [_jsx("span", { className: label, children: facet.label }), facet.options.map(o => chip(o, { idle: control, active: controlActive }, { title: text ? o.label : undefined, showCount: true }))] }, facet.key)));
48
49
  // The reset control leads the letters and says what it resets to.
49
- const alphaRow = letters.length > 0 && (_jsxs("div", { className: row, children: [_jsx("span", { className: label, children: alphaLabel }), letters.map(l => chip({
50
- key: l.value || 'all',
51
- href: l.href,
52
- active: l.active,
53
- text: l.label,
54
- n: l.count,
55
- ...(l.reset ? { title: `All ${l.count} pages` } : {}),
50
+ const alphaRow = letters.length > 0 && (_jsxs("div", { className: letterRow, children: [_jsx("span", { className: label, children: alphaLabel }), letters.map(l => chip(l, { idle: letterControl, active: letterControlActive }, {
51
+ title: l.reset ? `All ${l.count} pages` : letterCounts === 'title' ? pagesPhrase(l.count) : undefined,
52
+ showCount: letterCounts === 'inline',
56
53
  }))] }, "alpha"));
57
54
  // A landmark: every control in it narrows the list below, and a screen
58
55
  // reader user skipping between regions should be able to land on it.
@@ -149,7 +149,11 @@ export interface Taxonomy {
149
149
  metadataRows: (tagPath: string, page: {
150
150
  metadata?: unknown;
151
151
  }) => MetadataRow[];
152
- /** Whether a set has outgrown a grid and needs the A–Z index. */
152
+ /**
153
+ * Whether a set has outgrown a grid: the one size rule a hub's browsing
154
+ * controls read. `alphaControls` applies it; a sort toggle reads it too, with
155
+ * the same unfiltered count, so the two appear and vanish together.
156
+ */
153
157
  needsAlphaIndex: (pageCount: number) => boolean;
154
158
  href: (tagPath: string, state: CategoryState) => string;
155
159
  rankRelated: <T extends FacetablePage>(page: T, siblings: T[], tagPath: string, limit?: number) => RelatedRanking<T>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wiki-formant",
3
- "version": "0.59.0",
3
+ "version": "0.60.0",
4
4
  "description": "The portable half of a wiki: derived taxonomy and facet controls, a version-negotiating MCP transport, markdown twins, block rendering, a rich-text editor engine, conditional-GET plumbing, and the headers and CSP every origin sends.",
5
5
  "type": "module",
6
6
  "license": "MIT",