@solidrt/core 0.0.68 → 0.0.70

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@solidrt/core",
3
- "version": "0.0.68",
3
+ "version": "0.0.70",
4
4
  "license": "MIT",
5
5
  "funding": "https://github.com/sponsors/wellawaretech",
6
6
  "author": "Antoine van Wel",
@@ -33,8 +33,8 @@
33
33
  "AGENTS.md"
34
34
  ],
35
35
  "devDependencies": {
36
- "@solidrt/flux-types": "0.0.68",
37
- "@solidrt/test": "0.0.68"
36
+ "@solidrt/flux-types": "0.0.70",
37
+ "@solidrt/test": "0.0.70"
38
38
  },
39
39
  "peerDependencies": {
40
40
  "@solidjs/signals": "2.0.0-rc.13",
package/src/core.ts CHANGED
@@ -402,6 +402,30 @@ export function warmText(styles: tree.MeasureTextOptions[]): void {
402
402
  tree.warmText(styles)
403
403
  }
404
404
 
405
+ export type TextRenderingOptions = tree.TextRenderingOptions
406
+ export type TextRenderingPolicy = tree.TextRenderingPolicy
407
+
408
+ /**
409
+ * Changes how every text is rendered: the coverage-to-color mode and its
410
+ * gamma or contrast, and the stem darkening and hinting of the glyph cells. Each option
411
+ * given replaces its part of the current policy, the rest stays; every text
412
+ * redraws at the next frame. The defaults are what an app that says nothing
413
+ * gets; this is the knob for judging a policy by eye on a running app.
414
+ */
415
+ export function setTextRendering(options: TextRenderingOptions): void {
416
+ tree.setTextRendering(options)
417
+ }
418
+
419
+ /**
420
+ * The text rendering policy as it stands, resolved for this display: the
421
+ * shape setTextRendering takes, with a darkening or hinting left at its
422
+ * default read as the value the display scale gives it. What a settings
423
+ * screen shows; setting it back changes nothing on this display.
424
+ */
425
+ export function textRendering(): TextRenderingPolicy {
426
+ return tree.textRendering()
427
+ }
428
+
405
429
  /** One laid-out line from layoutNextLine. */
406
430
  export type TextLine = {
407
431
  /** Unit range [from, to) into prepared.units. */
@@ -426,6 +450,13 @@ export type TextLine = {
426
450
  * every glued piece that follows, plus the last piece's ink. What must fit on
427
451
  * the line for the unit to go on it.
428
452
  */
453
+ // Slack a line's width may be exceeded by and still fit, logical px: the
454
+ // width a laid-out box hands back is a float ulp off the width its text
455
+ // measured (the rendertree's layout::FIT_SLACK, the same value), so an
456
+ // exact fit wraps a word inside its own box. 1/64 px is a browser layout
457
+ // unit: far above any ulp drift, far below a visible overhang.
458
+ export const FIT_SLACK = 1 / 64
459
+
429
460
  export function unitInk(units: tree.TextUnit[], index: number): number {
430
461
  let ink = units[index]!.width
431
462
  let advance = 0
@@ -439,7 +470,8 @@ export function unitInk(units: tree.TextUnit[], index: number): number {
439
470
  /**
440
471
  * The next line of `prepared` from unit `cursor` that fits `width`, or null
441
472
  * when the text is used up. Greedy: units go on the line while the pen plus
442
- * the unit's ink stays within `width`; a hard break ends the line; a unit
473
+ * the unit's ink stays within `width` (a hair of slack for float drift
474
+ * included); a hard break ends the line; a unit
443
475
  * wider than `width` on its own goes on the line whole and overflows. Draw a
444
476
  * line as `<d-text x y w={line.width + 1}>{prepared.text.slice(line.start, line.end)}</d-text>`
445
477
  * with the same font options; its words are already shaped, so that is
@@ -458,7 +490,7 @@ export function layoutNextLine(prepared: tree.PreparedText, cursor: number, widt
458
490
  let unit = units[i]!
459
491
  // Glued pieces stay with the unit they continue: the whole wrap unit
460
492
  // (this piece through the last glued one) must fit for any of it to go on.
461
- if (i > cursor && !unit.glue && pen + unitInk(units, i) > width) break
493
+ if (i > cursor && !unit.glue && pen + unitInk(units, i) > width + FIT_SLACK) break
462
494
  pen += unit.advance
463
495
  if (unit.ascent > ascent) ascent = unit.ascent
464
496
  if (unit.descent > descent) descent = unit.descent
package/src/index.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  export * from "./renderer"
2
- export { setFocus, focusedNode, startTextInput, textInputActive, getFocusables, measureText, prepareText, warmText, layoutNextLine, unitInk, getBoundingBox, getBoundingBoxViewport, getLayoutBox, snapshotTexture, onPointerMove } from "./core"
3
- export type { BoundingBox, GlobalPointerEvent, TextLine } from "./core"
2
+ export { setFocus, focusedNode, startTextInput, textInputActive, getFocusables, measureText, prepareText, warmText, setTextRendering, textRendering, layoutNextLine, unitInk, getBoundingBox, getBoundingBoxViewport, getLayoutBox, snapshotTexture, onPointerMove } from "./core"
3
+ export type { BoundingBox, GlobalPointerEvent, TextLine, TextRenderingOptions, TextRenderingPolicy } from "./core"
4
4
  export { parseColor, mixColors, withAlpha, brightness, createLinearGradient, createRadialGradient } from "./color"
5
5
  export type { Gradient, GradientStop } from "./color"
6
6
  export { onFrame, frameTime, onBeforeRender, onLayout, onResize, onWindowFocus, onWindowBlur, onBack, onLink, onSuspend, onQuit, exit, background, registerProtocolHandler } from "./window"
@@ -25,6 +25,7 @@ export { Logo } from "./logo"
25
25
  export type { LogoProps } from "./logo"
26
26
  export type { SvgDocument, SvgDraw } from "./svg"
27
27
  export { createScroll } from "./scroll"
28
+ export { warnOnce } from "./warn"
28
29
  export type { Scroll, ScrollAxis, ScrollBehavior, ScrollOffset, ScrollOptions, ScrollToOptions } from "./scroll"
29
30
  export { arena } from "./arena"
30
31
  export type { ArenaOwner } from "./arena"
package/src/scroll.ts CHANGED
@@ -7,6 +7,7 @@
7
7
  import { createSignal } from "@solidjs/signals"
8
8
  import { getBoundingBox } from "./core"
9
9
  import { onLayout } from "./window"
10
+ import { warnOnce } from "./warn"
10
11
 
11
12
  export type ScrollAxis = "vertical" | "horizontal" | "both"
12
13
 
@@ -75,11 +76,9 @@ export function createScroll(
75
76
 
76
77
  // A scroll viewport with no explicit main-axis size resolves to 0 in flex
77
78
  // layout and its content silently vanishes - a classic trap (maxHeight alone
78
- // does not size it either). Detect it at measure time and warn once, with a
79
- // stack captured at creation so the warning points at the component that
80
- // built the scroller (the dev server remaps the frames to .tsx).
81
- let origin = new Error().stack ?? ""
82
- let warnedCollapsed = false
79
+ // does not size it either). Detect it at measure time and warn once, from
80
+ // the component that built the scroller (warnOnce captures its stack here).
81
+ let warnCollapsed = warnOnce()
83
82
 
84
83
  // Last measured overflow, refreshed each layout. scrollBy/scrollTo clamp
85
84
  // against these between layouts; onLayout re-clamps once new sizes are known.
@@ -110,17 +109,14 @@ export function createScroll(
110
109
  let vb = getBoundingBox(vp)
111
110
  let cb = getBoundingBox(ct)
112
111
  if (!vb || !cb) return
113
- if (!warnedCollapsed) {
114
- let zeroY = canY && vb.height === 0 && cb.height > 0
115
- let zeroX = canX && vb.width === 0 && cb.width > 0
116
- if (zeroY || zeroX) {
117
- warnedCollapsed = true
118
- let axisName = zeroY ? "height" : "width"
119
- console.warn(
120
- `Scroll container resolved to ${axisName} 0, so its content is invisible. ` +
121
- `Give it an explicit ${axisName} or flex; maxHeight/maxWidth alone does not size it.\n${origin}`,
122
- )
123
- }
112
+ let zeroY = canY && vb.height === 0 && cb.height > 0
113
+ let zeroX = canX && vb.width === 0 && cb.width > 0
114
+ if (zeroY || zeroX) {
115
+ let axisName = zeroY ? "height" : "width"
116
+ warnCollapsed(
117
+ `Scroll container resolved to ${axisName} 0, so its content is invisible. ` +
118
+ `Give it an explicit ${axisName} or flex; maxHeight/maxWidth alone does not size it.`,
119
+ )
124
120
  }
125
121
  maxX = Math.max(0, cb.width - vb.width)
126
122
  maxY = Math.max(0, cb.height - vb.height)
package/src/text-input.ts CHANGED
@@ -5,7 +5,7 @@
5
5
  // are policy and belong to the component (the "skin") that composes these.
6
6
 
7
7
  import { createMemo, createSignal, flush, untrack } from "@solidjs/signals"
8
- import { getLayoutBox, layoutNextLine, measureText, prepareText, unitInk } from "./core"
8
+ import { FIT_SLACK, getLayoutBox, layoutNextLine, measureText, prepareText, unitInk } from "./core"
9
9
  import type { MeasureTextOptions, PreparedText, TextRunRange, TextUnit } from "flux:rendertree"
10
10
  import { onLayout } from "./window"
11
11
 
@@ -542,12 +542,12 @@ export function createTextEditorLayout(
542
542
  // through as is.
543
543
  function splitWide(prepared: PreparedText, width: number): PreparedText {
544
544
  let all = prepared.units
545
- if (!all.some((u, i) => !u.glue && unitInk(all, i) > width)) return prepared
545
+ if (!all.some((u, i) => !u.glue && unitInk(all, i) > width + FIT_SLACK)) return prepared
546
546
  let wide = false
547
547
  let units: TextUnit[] = []
548
548
  for (let u = 0; u < all.length; u++) {
549
549
  let unit = all[u]!
550
- if (!unit.glue) wide = unitInk(all, u) > width
550
+ if (!unit.glue) wide = unitInk(all, u) > width + FIT_SLACK
551
551
  let stops = unit.carets
552
552
  if (!wide || !stops || stops.length <= 2) {
553
553
  units.push(unit)
package/src/types.d.ts CHANGED
@@ -103,6 +103,21 @@ export interface LayoutProps extends FlexboxProps, GridProps {
103
103
  * resolves against whatever relative element is above all of them.
104
104
  */
105
105
  position?: "relative" | "absolute"
106
+ /**
107
+ * CSS size containment: the element sizes as if it had no content. A
108
+ * texture or text measures as zero on every axis layout does not hand it,
109
+ * and a view's children add nothing to its box; they lay out inside
110
+ * whatever box it gets. The one CSS `contain` value with a meaning here.
111
+ * The element still needs a size from its own props or its parent (a
112
+ * `flexGrow` in a sized column), or it comes out empty.
113
+ *
114
+ * What takes the place of the web's `minHeight={0}` escape from the flex
115
+ * automatic minimum, declared on the element whose content would
116
+ * otherwise pin its ancestors: a fill-mode `<Scene>` or `<SpriteLayer>`
117
+ * leaf sets it, so the target that follows the leaf's box never feeds its
118
+ * pixel size back into layout.
119
+ */
120
+ contain?: "size"
106
121
 
107
122
  top?: Dimension
108
123
  right?: Dimension
package/src/warn.ts ADDED
@@ -0,0 +1,22 @@
1
+ // A development warning for a misuse that shows later than the code that
2
+ // caused it: a layout that comes out empty, an event that never arrives. The
3
+ // site of the misuse is the component that built the thing, not the layout
4
+ // pass that notices, so the warning carries that component's stack.
5
+
6
+ /**
7
+ * A once-only dev warning bound to the place it is created: `let warn =
8
+ * warnOnce()` where a component builds something whose misuse surfaces
9
+ * later (a scroller, a fill-mode scene), `warn(message)` from the layout
10
+ * or event read that notices. The first call reports, with the stack of the
11
+ * creation site under the message (the dev server remaps the frames to
12
+ * .tsx); later calls are silent.
13
+ */
14
+ export function warnOnce(): (message: string) => void {
15
+ let origin = new Error().stack ?? ""
16
+ let warned = false
17
+ return message => {
18
+ if (warned) return
19
+ warned = true
20
+ console.warn(`${message}\n${origin}`)
21
+ }
22
+ }