wiki-formant 0.30.0 → 0.31.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 +7 -0
- package/dist/react.d.ts +25 -0
- package/dist/react.js +46 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -258,6 +258,13 @@ const { query, setQuery, items, highlight, setHighlight, onKeyDown, reset } =
|
|
|
258
258
|
|
|
259
259
|
`useLinkPreview` is the Wikipedia-style hover card: one delegated listener rather than a component per link, which is what makes it viable over an article holding hundreds of anchors. Two wikis had written the same ninety lines — same intent delay, same grace period for the cursor to cross into the card, same clamp arithmetic, same cache. They differed in three things, and those three are the options: which anchors are eligible, what fetches a preview, and how tall the card is.
|
|
260
260
|
|
|
261
|
+
`useSnapRow` is a carousel as a row scrolled sideways under `scroll-snap-type: x mandatory`, so a finger swipes it with no script; AcuiQ's protocol card, which swapped one point for the next in state, could not be swiped at all. The hook is the rest: `step` for arrows and arrow keys, wrapping round at either end, `go(i)` for a dot or for opening a lightbox on the picture clicked, and `at` for the counter. It ships no markup and no class names, since miow's rows hold pictures and AcuiQ's hold whole acupoints.
|
|
262
|
+
|
|
263
|
+
```tsx
|
|
264
|
+
const { ref, onScroll, at, step, go } = useSnapRow();
|
|
265
|
+
<div ref={ref} onScroll={onScroll} className="row">{items}</div>
|
|
266
|
+
```
|
|
267
|
+
|
|
261
268
|
`useClickOutside` is fifteen lines and was in all three. Only one had the `offsetParent` check, and without it a container hidden at the current breakpoint still answers outside-clicks — so on a phone, a tap anywhere dismisses the popover the reader is looking at, because the hidden desktop copy got there first.
|
|
262
269
|
|
|
263
270
|
### The listbox contract
|
package/dist/react.d.ts
CHANGED
|
@@ -349,6 +349,31 @@ export declare function useCopy<K extends string = string>(revertAfter?: number)
|
|
|
349
349
|
copied: K | null;
|
|
350
350
|
copy: (text: string, key?: K) => Promise<boolean>;
|
|
351
351
|
};
|
|
352
|
+
export interface SnapRow<T extends HTMLElement> {
|
|
353
|
+
/** The row: give it `overflow-x: auto` and `scroll-snap-type: x mandatory`, and its children `scroll-snap-align: start`. */
|
|
354
|
+
ref: RefObject<T | null>;
|
|
355
|
+
/** The row's `onScroll`, which keeps `at` current. */
|
|
356
|
+
onScroll: () => void;
|
|
357
|
+
/** The item in view, or the last once the row is scrolled to its end: for a counter or dots. */
|
|
358
|
+
at: number;
|
|
359
|
+
/** Most of a view along, as an arrow or an arrow key moves it, wrapping round from either end. */
|
|
360
|
+
step: (dir: 1 | -1) => void;
|
|
361
|
+
/** Item `i` to the start of the row, as a dot moves it; `smooth: false` to open on it. */
|
|
362
|
+
go: (i: number, smooth?: boolean) => void;
|
|
363
|
+
}
|
|
364
|
+
/**
|
|
365
|
+
* A carousel as a row scrolled sideways and snapping into place. A finger
|
|
366
|
+
* swipes it with no script at all, which a carousel that swaps one item for
|
|
367
|
+
* the next in state cannot do: AcuiQ's protocol card moved only by its
|
|
368
|
+
* buttons. This is the rest, with no markup and no class names, since what the
|
|
369
|
+
* row holds differs from one use to the next.
|
|
370
|
+
*
|
|
371
|
+
* `step` scrolls by most of a view and lets the snap settle it, so a row of
|
|
372
|
+
* items narrower than itself pages a screenful at a time and a row of full-width
|
|
373
|
+
* ones moves one. It wraps at either end, because an arrow that does nothing
|
|
374
|
+
* there reads as broken. Motion honours `prefers-reduced-motion`.
|
|
375
|
+
*/
|
|
376
|
+
export declare function useSnapRow<T extends HTMLElement = HTMLDivElement>(): SnapRow<T>;
|
|
352
377
|
/**
|
|
353
378
|
* The one thing hooks cannot express, so it is worth owning once.
|
|
354
379
|
*
|
package/dist/react.js
CHANGED
|
@@ -698,6 +698,52 @@ export function useCopy(revertAfter = 2000) {
|
|
|
698
698
|
}, [revertAfter]);
|
|
699
699
|
return { copied, copy };
|
|
700
700
|
}
|
|
701
|
+
/**
|
|
702
|
+
* A carousel as a row scrolled sideways and snapping into place. A finger
|
|
703
|
+
* swipes it with no script at all, which a carousel that swaps one item for
|
|
704
|
+
* the next in state cannot do: AcuiQ's protocol card moved only by its
|
|
705
|
+
* buttons. This is the rest, with no markup and no class names, since what the
|
|
706
|
+
* row holds differs from one use to the next.
|
|
707
|
+
*
|
|
708
|
+
* `step` scrolls by most of a view and lets the snap settle it, so a row of
|
|
709
|
+
* items narrower than itself pages a screenful at a time and a row of full-width
|
|
710
|
+
* ones moves one. It wraps at either end, because an arrow that does nothing
|
|
711
|
+
* there reads as broken. Motion honours `prefers-reduced-motion`.
|
|
712
|
+
*/
|
|
713
|
+
export function useSnapRow() {
|
|
714
|
+
const ref = useRef(null);
|
|
715
|
+
const [at, setAt] = useState(0);
|
|
716
|
+
const onScroll = useCallback(() => {
|
|
717
|
+
const row = ref.current;
|
|
718
|
+
if (!row)
|
|
719
|
+
return;
|
|
720
|
+
const items = [...row.children];
|
|
721
|
+
const left = row.getBoundingClientRect().left;
|
|
722
|
+
const offsets = items.map(el => Math.abs(el.getBoundingClientRect().left - left));
|
|
723
|
+
setAt(row.scrollLeft >= snapEnd(row) - 1 ? items.length - 1 : offsets.indexOf(Math.min(...offsets)));
|
|
724
|
+
}, []);
|
|
725
|
+
const go = useCallback((i, smooth = true) => {
|
|
726
|
+
const row = ref.current;
|
|
727
|
+
const n = row?.children.length ?? 0;
|
|
728
|
+
const el = row?.children[((i % n) + n) % n];
|
|
729
|
+
if (row && el)
|
|
730
|
+
snapTo(row, row.scrollLeft + el.getBoundingClientRect().left - row.getBoundingClientRect().left, smooth);
|
|
731
|
+
}, []);
|
|
732
|
+
const step = useCallback((dir) => {
|
|
733
|
+
const row = ref.current;
|
|
734
|
+
if (!row)
|
|
735
|
+
return;
|
|
736
|
+
if (dir > 0 && row.scrollLeft >= snapEnd(row) - 1)
|
|
737
|
+
snapTo(row, 0);
|
|
738
|
+
else if (dir < 0 && row.scrollLeft <= 1)
|
|
739
|
+
snapTo(row, snapEnd(row));
|
|
740
|
+
else
|
|
741
|
+
snapTo(row, row.scrollLeft + dir * row.clientWidth * 0.8);
|
|
742
|
+
}, []);
|
|
743
|
+
return { ref, onScroll, at, step, go };
|
|
744
|
+
}
|
|
745
|
+
const snapEnd = (row) => row.scrollWidth - row.clientWidth;
|
|
746
|
+
const snapTo = (row, left, smooth = true) => row.scrollTo({ left, behavior: smooth && !matchMedia('(prefers-reduced-motion: reduce)').matches ? 'smooth' : 'auto' });
|
|
701
747
|
// ---- error boundary ---------------------------------------------------------
|
|
702
748
|
/**
|
|
703
749
|
* The one thing hooks cannot express, so it is worth owning once.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "wiki-formant",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.31.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, and conditional-GET plumbing.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|