@humanforest/frames 0.3.6 → 0.4.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 ADDED
@@ -0,0 +1,89 @@
1
+ # Changelog
2
+
3
+ Every @humanforest package shares one version, so one entry covers all seven packages. Newest first.
4
+
5
+ ## 0.4.0 — 2026-09-15
6
+
7
+ ### Features
8
+
9
+ - **docs**: the llms mirror prints a section kicker under its heading
10
+ - **frames**: FFitFrame fits a slot that sizes itself
11
+ - **charts**: a sparkline's area fades from its line to the floor
12
+ - **charts**: the calendar month rule boxes each month in a toned line
13
+ - **docs**: the llms mirror heads a non-prop table by its own column
14
+ - **docs**: the llms mirror names the module a page destructures
15
+ - **docs**: the llms mirror renders section loops
16
+ - **docs**: the llms mirror resolves plain strings a page imports
17
+ - **docs**: DemoStage puts a demo's controls above the specimen
18
+ - **docs**: the llms mirror names each code tab in a ProseCodeGroup
19
+ - **docs**: the llms mirror renders guidance cards and imported arrays
20
+
21
+ ### Fixes
22
+
23
+ - **ui**: the scroll fade follows content that changes after mount
24
+ - **ui**: map drawing places a corner only for a press on the map
25
+ - **ui**: the scale bar measures at the centre and hides off the globe
26
+ - **ui**: geolocate stays active through a location error while tracking
27
+ - **ui**: map search names only a rendered row in aria-activedescendant
28
+ - **ui**: a context menu that reopens elsewhere takes focus again
29
+ - **ui**: map overlays add layers after the frame, not during placement
30
+ - **ui**: the map context menu wires nothing on a non-interactive map
31
+ - **ui**: map search rings kept focus and never sends a pair to suggest
32
+ - **ui**: fullscreen reads nothing without a fullscreen API
33
+ - **ui**: geolocate is a plain action with track off
34
+ - **ui**: coordinates read Mapbox's 512px world and name the copied pair
35
+ - **docs**: chord, treemap and sankey demos keep their heights
36
+ - **charts**: resolveVar leaves the document alone without a DOM
37
+ - **docs**: the battery-swap stage keeps its start edge reachable
38
+ - **docs**: foundations and layout diagrams fit a phone
39
+ - **docs**: device frames scale down to fit a phone's pane
40
+ - **docs**: component specimens fit a phone's preview pane
41
+ - **filter**: the chip's number segments drop their steppers
42
+ - **docs**: the table page's pager drops its arrows on a phone
43
+ - **filter**: a chip wider than its bar wraps its segments
44
+ - **tokens**: the display role steps down to 4xl below 40rem
45
+ - **tokens**: long inline code wraps instead of overflowing its column
46
+ - **docs**: long inline code and prop types wrap instead of widening main
47
+ - **ui**: a vehicle mark's badge outline follows its edge
48
+ - **ui**: ramp swatches abut and round only the ramp's ends
49
+ - **docs**: a context switch keeps a page's live demos mounted
50
+ - **ui**: a bound popup closes on a second click and unbinds on unmount
51
+ - **ui**: a marker is a button only when it acts as one
52
+ - **ui**: FMap applies every padding side and logs each map error
53
+ - **ui**: a track slice no longer bridges a signal gap
54
+ - **ui**: track playback keeps the playhead when the speed changes
55
+ - **ui**: balance maps paint by magnitude, and legends label exactly
56
+ - **charts**: the tooltip snap looks for its panel on pointer entry
57
+ - **ui**: unavailable reasons name a state by its label
58
+ - **ui**: zones ignore a kind they do not know
59
+ - **ui**: a selected vehicle takes its new state on resync
60
+ - **docs**: a cross-page anchored link lands on its target
61
+ - **ui**: useMapReady never runs for a caller that has unmounted
62
+ - **tokens**: area fills keep their opacity under data-theme dark
63
+ - **charts**: crosshair dots sit on stacked band tops
64
+ - **charts**: a fitted London map labels only the areas it draws
65
+ - **charts**: a missing reading breaks a line sparkline
66
+ - **charts**: a dotted map's missing values add nothing
67
+ - **charts**: a selectable London map is reachable at every size
68
+ - **charts**: cell tracks and grids take one Tab stop, props read live
69
+ - **docs**: urgent vehicles draw on top of the fleet
70
+ - **ui**: alt shortcuts select, and deleted features leave the selection
71
+ - **ui**: a crossing edit springs back, and a drag reports once
72
+ - **ui**: selected markers rise, popups are named, one button opens them
73
+ - **ui**: the coordinate tile stays copyable as the pointer moves onto it
74
+ - **ui**: the scale bar's fill is as long as the distance it names
75
+
76
+ ## 0.3.7 — 2026-09-13
77
+
78
+ ### Features
79
+
80
+ - **ui**: add FMapContextMenu, a right-click menu on the map
81
+ - **charts**: chart frames and sparklines can be named for screen readers
82
+
83
+ ### Fixes
84
+
85
+ - **charts**: CellScale types what useCellScale returns
86
+ - **charts**: an empty distribution bar is still named, as "No data"
87
+ - **charts**: a bar sparkline warns that :curve="false" is ignored
88
+ - **charts**: a sparkline leaves non-finite readings off its scale
89
+ - **tokens**: the sub-logo builder passes noUncheckedIndexedAccess
package/package.json CHANGED
@@ -1,11 +1,12 @@
1
1
  {
2
2
  "name": "@humanforest/frames",
3
- "version": "0.3.6",
3
+ "version": "0.4.0",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "src",
7
7
  "README.md",
8
- "ATTRIBUTION.md"
8
+ "ATTRIBUTION.md",
9
+ "CHANGELOG.md"
9
10
  ],
10
11
  "exports": {
11
12
  ".": "./src/index.ts",
package/src/FFitFrame.vue CHANGED
@@ -18,8 +18,9 @@ import { computed, onBeforeUnmount, ref, watch } from 'vue';
18
18
 
19
19
  const props = withDefaults(
20
20
  defineProps<{
21
- /** Logical width the slot lays out at, whatever the space available. */
22
- width: number;
21
+ /** Logical width the slot lays out at, whatever the space available. Omit it for a slot with a
22
+ * fixed size of its own, such as a device frame, and its measured width is used. */
23
+ width?: number;
23
24
  /** Largest scale to draw at, so a wide page does not render the specimen at 1:1. */
24
25
  maxScale?: number;
25
26
  }>(),
@@ -33,6 +34,7 @@ const PAD_BOTTOM = 24;
33
34
 
34
35
  const inner = ref<HTMLElement | null>(null);
35
36
  const wrapper = ref<HTMLElement | null>(null);
37
+ const naturalW = ref(0);
36
38
  const naturalH = ref(0);
37
39
  const availW = ref(0);
38
40
 
@@ -40,7 +42,7 @@ const availW = ref(0);
40
42
  // component's mounted hook runs, and a missed attach leaves the wrapper unsized with no visible error.
41
43
  // No loop between the two: the slot's unscaled height is fixed by its own content, and the wrapper's
42
44
  // width is its block width — neither depends on the scale derived from them.
43
- const innerRO = new ResizeObserver(([e]) => { naturalH.value = e!.contentRect.height; });
45
+ const innerRO = new ResizeObserver(([e]) => { naturalW.value = e!.contentRect.width; naturalH.value = e!.contentRect.height; });
44
46
  const wrapRO = new ResizeObserver(([e]) => { availW.value = e!.contentRect.width; });
45
47
  watch(inner, (el) => { innerRO.disconnect(); if (el) innerRO.observe(el); }, { immediate: true, flush: 'post' });
46
48
  watch(wrapper, (el) => { wrapRO.disconnect(); if (el) wrapRO.observe(el); }, { immediate: true, flush: 'post' });
@@ -48,7 +50,12 @@ onBeforeUnmount(() => { innerRO.disconnect(); wrapRO.disconnect(); });
48
50
 
49
51
  // ★ SCALE TO FIT, capped. A fixed factor overflows its container the moment the page is narrower than
50
52
  // width × scale — the slot's layout width never changes, so nothing else absorbs it.
51
- const fit = computed(() => (availW.value ? Math.min(props.maxScale, availW.value / props.width) : props.maxScale));
53
+ // The wrapper needs a width of its own to fit into: inside a shrink-to-fit parent it takes the slot's
54
+ // width, and the frame never scales.
55
+ const fit = computed(() => {
56
+ const w = props.width ?? naturalW.value;
57
+ return availW.value && w ? Math.min(props.maxScale, availW.value / w) : props.maxScale;
58
+ });
52
59
 
53
60
  // Height the scaled frame actually takes on the page. 0 until measured, so nothing jumps.
54
61
  const reservedH = computed(() =>