@opentagcloud/core 0.3.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 +78 -0
- package/dist/estimate.d.ts +19 -0
- package/dist/estimate.js +48 -0
- package/dist/estimate.js.map +1 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -0
- package/dist/layout.d.ts +59 -0
- package/dist/layout.js +595 -0
- package/dist/layout.js.map +1 -0
- package/dist/opentagcloud.vanilla.js +838 -0
- package/dist/prepare.d.ts +78 -0
- package/dist/prepare.js +80 -0
- package/dist/prepare.js.map +1 -0
- package/dist/render.d.ts +37 -0
- package/dist/render.js +73 -0
- package/dist/render.js.map +1 -0
- package/dist/rng.d.ts +7 -0
- package/dist/rng.js +20 -0
- package/dist/rng.js.map +1 -0
- package/dist/styles.css +52 -0
- package/dist/styles.d.ts +14 -0
- package/dist/styles.js +76 -0
- package/dist/styles.js.map +1 -0
- package/dist/types.d.ts +31 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/umd.d.ts +1 -0
- package/dist/umd.js +8 -0
- package/dist/umd.js.map +1 -0
- package/dist/vanilla.d.ts +44 -0
- package/dist/vanilla.js +127 -0
- package/dist/vanilla.js.map +1 -0
- package/package.json +63 -0
package/README.md
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# @opentagcloud/core
|
|
2
|
+
|
|
3
|
+
Framework-agnostic core of [openTagCloud](https://github.com/hkoren/openTagCloud)
|
|
4
|
+
— a dependency-free, self-packing, SSR-friendly tag cloud. This package is the
|
|
5
|
+
layout engine plus a vanilla-JS renderer; framework adapters
|
|
6
|
+
(`opentagcloud` for Svelte, `@opentagcloud/react`, `@opentagcloud/vue`,
|
|
7
|
+
`@opentagcloud/angular`, `@opentagcloud/solid`, `@opentagcloud/next`) are thin
|
|
8
|
+
wrappers over it.
|
|
9
|
+
|
|
10
|
+
## Vanilla usage
|
|
11
|
+
|
|
12
|
+
```js
|
|
13
|
+
import { renderTagCloud } from '@opentagcloud/core';
|
|
14
|
+
|
|
15
|
+
const cloud = renderTagCloud(document.querySelector('#cloud'), [
|
|
16
|
+
{ label: 'JavaScript', weight: 95, href: '/tags/javascript' },
|
|
17
|
+
{ label: 'TypeScript', weight: 88 },
|
|
18
|
+
{ label: 'Rust', weight: 60, color: 'tomato' },
|
|
19
|
+
]);
|
|
20
|
+
|
|
21
|
+
cloud.update(newItems); // replace items and re-pack
|
|
22
|
+
cloud.destroy(); // stop observing, remove tags
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Give the container a sized parent; the cloud packs to its width and grows its
|
|
26
|
+
`min-height` to fit. Styles are injected at runtime — no CSS setup needed. For
|
|
27
|
+
server-rendered pages, also ship `@opentagcloud/core/styles.css` so the no-JS
|
|
28
|
+
fallback (justified inline flow) is styled before hydration.
|
|
29
|
+
|
|
30
|
+
## Script tag / custom element (no build step)
|
|
31
|
+
|
|
32
|
+
The package also ships a browser global (`dist/opentagcloud.vanilla.js`, the
|
|
33
|
+
`unpkg`/`jsdelivr` entry) that registers a light-DOM `<otc-tag-cloud>` custom
|
|
34
|
+
element and exposes `openTagCloud.mount(container, items, options)`:
|
|
35
|
+
|
|
36
|
+
```html
|
|
37
|
+
<script src="https://unpkg.com/@opentagcloud/core/dist/opentagcloud.vanilla.js"></script>
|
|
38
|
+
|
|
39
|
+
<otc-tag-cloud
|
|
40
|
+
style="height: 320px"
|
|
41
|
+
items='[{ "label": "Rust", "weight": 60 }]'
|
|
42
|
+
>
|
|
43
|
+
</otc-tag-cloud>
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
`mount()` and `defineElement()` are also exported from the package root and the
|
|
47
|
+
`./vanilla` subpath for ES-module consumers (call `defineElement()` to register
|
|
48
|
+
the element). Both are thin wrappers over `renderTagCloud` — same engine, same
|
|
49
|
+
markup contract. Try the
|
|
50
|
+
[live vanilla example](https://hkoren.github.io/openTagCloud/) or view the
|
|
51
|
+
[`example source`](examples/vanilla.html).
|
|
52
|
+
|
|
53
|
+
## SSR height estimation
|
|
54
|
+
|
|
55
|
+
`estimateCloudHeight(items, width, options?)` is pure and DOM-free: emit it as
|
|
56
|
+
the container's `min-height` during SSR to reserve space and avoid layout
|
|
57
|
+
shift (roughly ±25% of the real packed height).
|
|
58
|
+
|
|
59
|
+
## Building your own adapter
|
|
60
|
+
|
|
61
|
+
- `prepareTags(items, { minPx, maxPx })` — pure and DOM-free: returns each
|
|
62
|
+
tag's font size, opacity, key, display text, and inline style. Call it in
|
|
63
|
+
your template layer (works during SSR).
|
|
64
|
+
- Render one element per prepared tag with its `className`, `style`, `title`,
|
|
65
|
+
and `data-fs` / `data-weight` / `data-key` attributes, inside a container
|
|
66
|
+
with class `otc-cloud`. Render its `parts` as the content — `nowrap` parts go
|
|
67
|
+
in a `<span class="otc-nb">` so lines never break at a hyphen while the DOM
|
|
68
|
+
text stays byte-identical to the label.
|
|
69
|
+
- `new TagCloudLayout(container, { fill }).attach()` on mount; `refresh()`
|
|
70
|
+
after the tags change; `destroy()` on teardown. All methods are no-ops
|
|
71
|
+
without a DOM.
|
|
72
|
+
|
|
73
|
+
See the [project README](https://github.com/hkoren/openTagCloud#readme) for
|
|
74
|
+
props, theming, and how the packer works.
|
|
75
|
+
|
|
76
|
+
## License
|
|
77
|
+
|
|
78
|
+
MIT © Henry Koren
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { TagCloudItem } from './types.js';
|
|
2
|
+
import { type PrepareOptions } from './prepare.js';
|
|
3
|
+
/**
|
|
4
|
+
* Estimate the packed height of a cloud for a given container width — without
|
|
5
|
+
* a DOM, so it can run during SSR. Emit it as the container's `min-height` to
|
|
6
|
+
* reserve space before hydration and avoid layout shift (CLS):
|
|
7
|
+
*
|
|
8
|
+
* ```svelte
|
|
9
|
+
* <div style="min-height: {estimateCloudHeight(items, 720)}px">
|
|
10
|
+
* <TagCloud {items} />
|
|
11
|
+
* </div>
|
|
12
|
+
* ```
|
|
13
|
+
*
|
|
14
|
+
* It mirrors the packer's own area-based box-height formula, with text
|
|
15
|
+
* measured by a character-width heuristic instead of the real font metrics,
|
|
16
|
+
* so expect roughly ±25% — enough to absorb the fallback→packed reflow. A
|
|
17
|
+
* fixed-height container is still the zero-shift option.
|
|
18
|
+
*/
|
|
19
|
+
export declare function estimateCloudHeight(items: TagCloudItem[], width: number, options?: PrepareOptions): number;
|
package/dist/estimate.js
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { prepareTags } from './prepare.js';
|
|
2
|
+
import { PAD, LOOSEN, widthFactor } from './layout.js';
|
|
3
|
+
/**
|
|
4
|
+
* Estimate the packed height of a cloud for a given container width — without
|
|
5
|
+
* a DOM, so it can run during SSR. Emit it as the container's `min-height` to
|
|
6
|
+
* reserve space before hydration and avoid layout shift (CLS):
|
|
7
|
+
*
|
|
8
|
+
* ```svelte
|
|
9
|
+
* <div style="min-height: {estimateCloudHeight(items, 720)}px">
|
|
10
|
+
* <TagCloud {items} />
|
|
11
|
+
* </div>
|
|
12
|
+
* ```
|
|
13
|
+
*
|
|
14
|
+
* It mirrors the packer's own area-based box-height formula, with text
|
|
15
|
+
* measured by a character-width heuristic instead of the real font metrics,
|
|
16
|
+
* so expect roughly ±25% — enough to absorb the fallback→packed reflow. A
|
|
17
|
+
* fixed-height container is still the zero-shift option.
|
|
18
|
+
*/
|
|
19
|
+
export function estimateCloudHeight(items, width, options = {}) {
|
|
20
|
+
if (!items.length || width < 2)
|
|
21
|
+
return 0;
|
|
22
|
+
const wf = widthFactor(width);
|
|
23
|
+
const wide = width >= 380;
|
|
24
|
+
// average advance width of a bold system-font glyph, in em
|
|
25
|
+
const GLYPH = 0.58;
|
|
26
|
+
let area = 0;
|
|
27
|
+
let tallest = 0;
|
|
28
|
+
for (const p of prepareTags(items, options)) {
|
|
29
|
+
const fs = Math.max(8, p.fontPx * wf);
|
|
30
|
+
const textW = p.text.length * fs * GLYPH;
|
|
31
|
+
let w;
|
|
32
|
+
let lines = 1;
|
|
33
|
+
if (wide) {
|
|
34
|
+
// nowrap; the packer shrinks anything wider than the container
|
|
35
|
+
w = Math.min(textW, width);
|
|
36
|
+
}
|
|
37
|
+
else {
|
|
38
|
+
const maxW = Math.min(6.5 * fs, width);
|
|
39
|
+
lines = Math.max(1, Math.ceil(textW / maxW));
|
|
40
|
+
w = Math.min(textW, maxW);
|
|
41
|
+
}
|
|
42
|
+
const h = lines * fs * 0.95; // line-height 0.95
|
|
43
|
+
area += (w + PAD) * (h + PAD);
|
|
44
|
+
tallest = Math.max(tallest, h);
|
|
45
|
+
}
|
|
46
|
+
return Math.ceil(Math.max((area * LOOSEN) / width, tallest));
|
|
47
|
+
}
|
|
48
|
+
//# sourceMappingURL=estimate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"estimate.js","sourceRoot":"","sources":["../src/estimate.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,EAAuB,MAAM,cAAc,CAAC;AAChE,OAAO,EAAE,GAAG,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAEvD;;;;;;;;;;;;;;;GAeG;AACH,MAAM,UAAU,mBAAmB,CACjC,KAAqB,EACrB,KAAa,EACb,UAA0B,EAAE;IAE5B,IAAI,CAAC,KAAK,CAAC,MAAM,IAAI,KAAK,GAAG,CAAC;QAAE,OAAO,CAAC,CAAC;IACzC,MAAM,EAAE,GAAG,WAAW,CAAC,KAAK,CAAC,CAAC;IAC9B,MAAM,IAAI,GAAG,KAAK,IAAI,GAAG,CAAC;IAC1B,2DAA2D;IAC3D,MAAM,KAAK,GAAG,IAAI,CAAC;IACnB,IAAI,IAAI,GAAG,CAAC,CAAC;IACb,IAAI,OAAO,GAAG,CAAC,CAAC;IAChB,KAAK,MAAM,CAAC,IAAI,WAAW,CAAC,KAAK,EAAE,OAAO,CAAC,EAAE,CAAC;QAC5C,MAAM,EAAE,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC,MAAM,GAAG,EAAE,CAAC,CAAC;QACtC,MAAM,KAAK,GAAG,CAAC,CAAC,IAAI,CAAC,MAAM,GAAG,EAAE,GAAG,KAAK,CAAC;QACzC,IAAI,CAAS,CAAC;QACd,IAAI,KAAK,GAAG,CAAC,CAAC;QACd,IAAI,IAAI,EAAE,CAAC;YACT,+DAA+D;YAC/D,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;QAC7B,CAAC;aAAM,CAAC;YACN,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,GAAG,EAAE,EAAE,KAAK,CAAC,CAAC;YACvC,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,CAAC,CAAC;YAC7C,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;QAC5B,CAAC;QACD,MAAM,CAAC,GAAG,KAAK,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC,mBAAmB;QAChD,IAAI,IAAI,CAAC,CAAC,GAAG,GAAG,CAAC,GAAG,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC;QAC9B,OAAO,GAAG,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;IACjC,CAAC;IACD,OAAO,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,GAAG,MAAM,CAAC,GAAG,KAAK,EAAE,OAAO,CAAC,CAAC,CAAC;AAC/D,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export type { TagCloudItem, Fill } from './types.js';
|
|
2
|
+
export { prepareTags, keyOf } from './prepare.js';
|
|
3
|
+
export type { PreparedTag, PrepareOptions, LabelPart } from './prepare.js';
|
|
4
|
+
export { TagCloudLayout } from './layout.js';
|
|
5
|
+
export type { TagCloudLayoutOptions } from './layout.js';
|
|
6
|
+
export { renderTagCloud, createTagElement } from './render.js';
|
|
7
|
+
export type { RenderOptions, TagCloudHandle } from './render.js';
|
|
8
|
+
export { injectStyles, TAG_CLOUD_CSS } from './styles.js';
|
|
9
|
+
export { makeRng } from './rng.js';
|
|
10
|
+
export { mount, defineElement } from './vanilla.js';
|
|
11
|
+
export type { MountOptions, CloudHandle } from './vanilla.js';
|
|
12
|
+
export { estimateCloudHeight } from './estimate.js';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export { prepareTags, keyOf } from './prepare.js';
|
|
2
|
+
export { TagCloudLayout } from './layout.js';
|
|
3
|
+
export { renderTagCloud, createTagElement } from './render.js';
|
|
4
|
+
export { injectStyles, TAG_CLOUD_CSS } from './styles.js';
|
|
5
|
+
export { makeRng } from './rng.js';
|
|
6
|
+
export { mount, defineElement } from './vanilla.js';
|
|
7
|
+
export { estimateCloudHeight } from './estimate.js';
|
|
8
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,EAAE,KAAK,EAAE,MAAM,cAAc,CAAC;AAElD,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAE7C,OAAO,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAE/D,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC1D,OAAO,EAAE,OAAO,EAAE,MAAM,UAAU,CAAC;AACnC,OAAO,EAAE,KAAK,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAEpD,OAAO,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAC"}
|
package/dist/layout.d.ts
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import type { Fill } from './types.js';
|
|
2
|
+
export interface TagCloudLayoutOptions {
|
|
3
|
+
/** Also spread terms vertically to fill the container's height. */
|
|
4
|
+
fill?: Fill;
|
|
5
|
+
/**
|
|
6
|
+
* Add the component stylesheet to the document on `attach()` (default true).
|
|
7
|
+
* Set false if you serve `@opentagcloud/core/styles.css` yourself.
|
|
8
|
+
*/
|
|
9
|
+
injectStyles?: boolean;
|
|
10
|
+
/**
|
|
11
|
+
* Keep unchanged tags in place when `refresh()` runs after an item update:
|
|
12
|
+
* tags whose measured box still fits at its old position stay put; only
|
|
13
|
+
* new, resized, or displaced tags are re-placed (seeded from their old
|
|
14
|
+
* position, so they move minimally). Falls back to a full re-pack when the
|
|
15
|
+
* width changed or more than 40% of tags would move. Off by default — a
|
|
16
|
+
* full re-pack always produces the canonical deterministic layout.
|
|
17
|
+
*/
|
|
18
|
+
incremental?: boolean;
|
|
19
|
+
}
|
|
20
|
+
/** @internal Gap between tag boxes; shared with estimateCloudHeight(). */
|
|
21
|
+
export declare const PAD = 5;
|
|
22
|
+
/** @internal Area slack factor used to derive the packing box height. */
|
|
23
|
+
export declare const LOOSEN = 1.4;
|
|
24
|
+
/** @internal container width → font: shrink on narrow (mobile), grow modestly when wide. */
|
|
25
|
+
export declare const widthFactor: (w: number) => number;
|
|
26
|
+
/**
|
|
27
|
+
* The self-packing layout engine. Framework-free: it operates on a container
|
|
28
|
+
* element whose children carry class `otc-tag` plus three data attributes —
|
|
29
|
+
* `data-fs` (base font px), `data-weight`, and `data-key` (scatter seed) — all
|
|
30
|
+
* emitted by `prepareTags()`. How those elements got into the DOM (a framework
|
|
31
|
+
* template, `renderTagCloud()`, or server-rendered HTML) is irrelevant.
|
|
32
|
+
*
|
|
33
|
+
* Lifecycle: construct with the container, call `attach()` once it's in the
|
|
34
|
+
* document, `refresh()` after the tag elements change, `destroy()` on teardown.
|
|
35
|
+
* All methods are no-ops without a DOM, so adapters can call them under SSR.
|
|
36
|
+
*
|
|
37
|
+
* Re-packs and re-distributions animate: moved tags FLIP from their previous
|
|
38
|
+
* visual position, new tags scale in. Duration/easing come from the
|
|
39
|
+
* `--otc-move-transition` custom property (default `250ms` ease-out); set it
|
|
40
|
+
* to `0s` or `none` to disable, and `prefers-reduced-motion: reduce` disables
|
|
41
|
+
* it automatically. The initial pack never animates.
|
|
42
|
+
*/
|
|
43
|
+
export declare class TagCloudLayout {
|
|
44
|
+
#private;
|
|
45
|
+
constructor(root: HTMLElement, options?: TagCloudLayoutOptions);
|
|
46
|
+
/** Start observing the container and pack the initial layout. */
|
|
47
|
+
attach(): void;
|
|
48
|
+
/** Re-pack after the tag elements changed (items added/removed/re-weighted). */
|
|
49
|
+
refresh(): void;
|
|
50
|
+
/** Change the fill mode; only term positions move, never the container height. */
|
|
51
|
+
setFill(fill: Fill | undefined): void;
|
|
52
|
+
/** Stop observing. The current positions are left in place. */
|
|
53
|
+
destroy(): void;
|
|
54
|
+
pack(): void;
|
|
55
|
+
distribute(flipFrom?: Map<string, {
|
|
56
|
+
x: number;
|
|
57
|
+
y: number;
|
|
58
|
+
}> | null): void;
|
|
59
|
+
}
|