umaki 0.12.0 → 0.13.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
@@ -106,6 +106,7 @@ console.log(breakpoint); // e.g. 768 (default) or custom value if set
106
106
  - [getRandomInt](#getrandomint)
107
107
  - [getRelativeTime](#getrelativetime)
108
108
  - [getRem](#getrem)
109
+ - [getRenderedLineHeight](#getrenderedlineheight)
109
110
  - [getScrollbarWidth](#getscrollbarwidth)
110
111
  - [getSessionStorage](#getsessionstorage)
111
112
  - [getStringLength](#getstringlength)
@@ -619,6 +620,48 @@ console.log(remValue); // '1rem'
619
620
 
620
621
  [View file →](src/libs/get/get-rem.ts)
621
622
 
623
+ ### getRenderedLineHeight
624
+
625
+ Measures the line-height **as it is actually rendered** (in CSS pixels) for a target element by inserting a hidden probe element with the same font-related styles and dividing its multi-line height by the sample line count.
626
+
627
+ This works around an iOS Safari quirk ([WebKit Bug #225695](https://bugs.webkit.org/show_bug.cgi?id=225695)) where fractional `line-height` values are floored to integer pixels at paint time. Stacking-line UIs that rely on `1lh` (e.g. `mask-image: repeating-linear-gradient` rules) drift on iOS Safari but match `getRenderedLineHeight` output. Returns `NaN` in SSR environments or when the probe cannot be measured.
628
+
629
+ The caller is responsible for awaiting `document.fonts.ready` before measuring when font-loaded accuracy matters.
630
+
631
+ ```ts
632
+ import { getRenderedLineHeight } from "umaki";
633
+
634
+ const textEl = document.querySelector<HTMLElement>(".text");
635
+ if (!textEl) return;
636
+
637
+ await document.fonts.ready;
638
+
639
+ const lh = getRenderedLineHeight(textEl);
640
+ if (Number.isFinite(lh)) {
641
+ textEl.style.setProperty("--text-line-height", `${lh}px`);
642
+ }
643
+ ```
644
+
645
+ ```css
646
+ .text {
647
+ mask-image: repeating-linear-gradient(
648
+ to bottom,
649
+ transparent 0,
650
+ transparent calc(var(--text-line-height, 1lh) - 0.5px),
651
+ #000 calc(var(--text-line-height, 1lh) - 0.5px),
652
+ #000 var(--text-line-height, 1lh)
653
+ );
654
+ }
655
+ ```
656
+
657
+ You can adjust the sampling resolution via `sampleLines` (default `20`, minimum `2`):
658
+
659
+ ```ts
660
+ const lh = getRenderedLineHeight(textEl, { sampleLines: 40 });
661
+ ```
662
+
663
+ [View file →](src/libs/get/get-rendered-line-height.ts)
664
+
622
665
  ### getScrollbarWidth
623
666
 
624
667
  A function that retrieves the width of the scrollbar.
package/dist/index.cjs.js CHANGED
@@ -25,9 +25,10 @@ const getGravatarUrl = require("./libs/get/get-gravatar-url.cjs.js");
25
25
  const getOrientation = require("./libs/get/get-orientation.cjs.js");
26
26
  const getParentList = require("./libs/get/get-parent-list.cjs.js");
27
27
  const getQueryParams = require("./libs/get/get-query-params.cjs.js");
28
- const getRelativeTime = require("./libs/get/get-relative-time.cjs.js");
29
28
  const getRandomInt = require("./libs/get/get-random-int.cjs.js");
29
+ const getRelativeTime = require("./libs/get/get-relative-time.cjs.js");
30
30
  const getRem = require("./libs/get/get-rem.cjs.js");
31
+ const getRenderedLineHeight = require("./libs/get/get-rendered-line-height.cjs.js");
31
32
  const getScrollbarWidth = require("./libs/get/get-scrollbar-width.cjs.js");
32
33
  const getSessionStorage = require("./libs/get/get-session-storage.cjs.js");
33
34
  const getStringLength = require("./libs/get/get-string-length.cjs.js");
@@ -87,9 +88,10 @@ exports.getGravatarUrl = getGravatarUrl.getGravatarUrl;
87
88
  exports.getOrientation = getOrientation.getOrientation;
88
89
  exports.getParentList = getParentList.getParentList;
89
90
  exports.getQueryParams = getQueryParams.getQueryParams;
90
- exports.getRelativeTime = getRelativeTime.getRelativeTime;
91
91
  exports.getRandomInt = getRandomInt.getRandomInt;
92
+ exports.getRelativeTime = getRelativeTime.getRelativeTime;
92
93
  exports.getRem = getRem.getRem;
94
+ exports.getRenderedLineHeight = getRenderedLineHeight.getRenderedLineHeight;
93
95
  exports.getScrollbarWidth = getScrollbarWidth.getScrollbarWidth;
94
96
  exports.getSessionStorage = getSessionStorage.getSessionStorage;
95
97
  exports.getStringLength = getStringLength.getStringLength;
package/dist/index.es.js CHANGED
@@ -23,9 +23,10 @@ import { getGravatarUrl } from "./libs/get/get-gravatar-url.es.js";
23
23
  import { getOrientation } from "./libs/get/get-orientation.es.js";
24
24
  import { getParentList } from "./libs/get/get-parent-list.es.js";
25
25
  import { getQueryParams } from "./libs/get/get-query-params.es.js";
26
- import { getRelativeTime } from "./libs/get/get-relative-time.es.js";
27
26
  import { getRandomInt } from "./libs/get/get-random-int.es.js";
27
+ import { getRelativeTime } from "./libs/get/get-relative-time.es.js";
28
28
  import { getRem } from "./libs/get/get-rem.es.js";
29
+ import { getRenderedLineHeight } from "./libs/get/get-rendered-line-height.es.js";
29
30
  import { getScrollbarWidth } from "./libs/get/get-scrollbar-width.es.js";
30
31
  import { getSessionStorage } from "./libs/get/get-session-storage.es.js";
31
32
  import { getStringLength } from "./libs/get/get-string-length.es.js";
@@ -80,6 +81,7 @@ export {
80
81
  getRandomInt,
81
82
  getRelativeTime,
82
83
  getRem,
84
+ getRenderedLineHeight,
83
85
  getScrollbarWidth,
84
86
  getSessionStorage,
85
87
  getStringLength,
@@ -0,0 +1,57 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
+ const DEFAULT_SAMPLE_LINES = 20;
4
+ const MIN_SAMPLE_LINES = 2;
5
+ const MAX_SAMPLE_LINES = 100;
6
+ const normalizeSampleLines = (value) => {
7
+ const raw = value ?? DEFAULT_SAMPLE_LINES;
8
+ if (!Number.isFinite(raw)) return DEFAULT_SAMPLE_LINES;
9
+ return Math.min(MAX_SAMPLE_LINES, Math.max(MIN_SAMPLE_LINES, Math.floor(raw)));
10
+ };
11
+ const getRenderedLineHeight = (element, options) => {
12
+ if (typeof window === "undefined") return Number.NaN;
13
+ const sampleLines = normalizeSampleLines(options?.sampleLines);
14
+ const ownerDocument = element.ownerDocument;
15
+ const host = ownerDocument?.body;
16
+ const view = ownerDocument?.defaultView;
17
+ if (!host || !view) return Number.NaN;
18
+ const cs = view.getComputedStyle(element);
19
+ const probe = ownerDocument.createElement("div");
20
+ const style = probe.style;
21
+ style.position = "absolute";
22
+ style.left = "-9999px";
23
+ style.top = "0";
24
+ style.visibility = "hidden";
25
+ style.pointerEvents = "none";
26
+ style.margin = "0";
27
+ style.padding = "0";
28
+ style.border = "0";
29
+ style.whiteSpace = "pre";
30
+ style.fontFamily = cs.fontFamily;
31
+ style.fontSize = cs.fontSize;
32
+ style.fontWeight = cs.fontWeight;
33
+ style.fontStyle = cs.fontStyle;
34
+ style.lineHeight = cs.lineHeight;
35
+ style.letterSpacing = cs.letterSpacing;
36
+ style.fontFeatureSettings = cs.fontFeatureSettings;
37
+ if (cs.fontVariationSettings) {
38
+ style.fontVariationSettings = cs.fontVariationSettings;
39
+ }
40
+ for (let i = 0; i < sampleLines; i++) {
41
+ if (i > 0) probe.appendChild(ownerDocument.createElement("br"));
42
+ probe.appendChild(ownerDocument.createTextNode("X"));
43
+ }
44
+ host.appendChild(probe);
45
+ try {
46
+ const probeHeight = probe.getBoundingClientRect().height;
47
+ if (probeHeight <= 0) return Number.NaN;
48
+ return probeHeight / sampleLines;
49
+ } catch {
50
+ return Number.NaN;
51
+ } finally {
52
+ if (probe.parentNode === host) {
53
+ host.removeChild(probe);
54
+ }
55
+ }
56
+ };
57
+ exports.getRenderedLineHeight = getRenderedLineHeight;
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Options for {@link getRenderedLineHeight}.
3
+ */
4
+ export type GetRenderedLineHeightOptions = {
5
+ /**
6
+ * Number of lines used to measure the rendered line-height. Higher values
7
+ * yield a more precise per-line average. Clamped to the range [2, 100];
8
+ * non-finite values fall back to the default.
9
+ *
10
+ * @default 20
11
+ */
12
+ sampleLines?: number;
13
+ };
14
+ /**
15
+ * Measures the line-height as it is actually rendered (in CSS pixels) for the
16
+ * given element.
17
+ *
18
+ * On iOS Safari, fractional `line-height` values are floored to integer pixels
19
+ * at paint time, which makes `getComputedStyle(...).lineHeight` and the
20
+ * `1lh` unit drift from the visual baseline. This function inserts a hidden
21
+ * probe element with the same font/line-height related styles, measures the
22
+ * total height across multiple lines, and divides by the line count to obtain
23
+ * the actually rendered per-line height.
24
+ *
25
+ * Returns `NaN` in non-DOM environments (SSR), or when the probe cannot be
26
+ * measured (no body, no defaultView, zero/negative height, or measurement
27
+ * exception).
28
+ *
29
+ * The caller is responsible for waiting on `document.fonts.ready` before
30
+ * calling this function when accurate font-loaded measurements matter.
31
+ *
32
+ * @param element - The element whose rendered line-height should be measured.
33
+ * @param options - Measurement options.
34
+ * @returns The rendered line-height in CSS pixels, or `NaN` when measurement
35
+ * is not possible.
36
+ */
37
+ export declare const getRenderedLineHeight: (element: HTMLElement, options?: GetRenderedLineHeightOptions) => number;
@@ -0,0 +1,57 @@
1
+ const DEFAULT_SAMPLE_LINES = 20;
2
+ const MIN_SAMPLE_LINES = 2;
3
+ const MAX_SAMPLE_LINES = 100;
4
+ const normalizeSampleLines = (value) => {
5
+ const raw = value ?? DEFAULT_SAMPLE_LINES;
6
+ if (!Number.isFinite(raw)) return DEFAULT_SAMPLE_LINES;
7
+ return Math.min(MAX_SAMPLE_LINES, Math.max(MIN_SAMPLE_LINES, Math.floor(raw)));
8
+ };
9
+ const getRenderedLineHeight = (element, options) => {
10
+ if (typeof window === "undefined") return Number.NaN;
11
+ const sampleLines = normalizeSampleLines(options?.sampleLines);
12
+ const ownerDocument = element.ownerDocument;
13
+ const host = ownerDocument?.body;
14
+ const view = ownerDocument?.defaultView;
15
+ if (!host || !view) return Number.NaN;
16
+ const cs = view.getComputedStyle(element);
17
+ const probe = ownerDocument.createElement("div");
18
+ const style = probe.style;
19
+ style.position = "absolute";
20
+ style.left = "-9999px";
21
+ style.top = "0";
22
+ style.visibility = "hidden";
23
+ style.pointerEvents = "none";
24
+ style.margin = "0";
25
+ style.padding = "0";
26
+ style.border = "0";
27
+ style.whiteSpace = "pre";
28
+ style.fontFamily = cs.fontFamily;
29
+ style.fontSize = cs.fontSize;
30
+ style.fontWeight = cs.fontWeight;
31
+ style.fontStyle = cs.fontStyle;
32
+ style.lineHeight = cs.lineHeight;
33
+ style.letterSpacing = cs.letterSpacing;
34
+ style.fontFeatureSettings = cs.fontFeatureSettings;
35
+ if (cs.fontVariationSettings) {
36
+ style.fontVariationSettings = cs.fontVariationSettings;
37
+ }
38
+ for (let i = 0; i < sampleLines; i++) {
39
+ if (i > 0) probe.appendChild(ownerDocument.createElement("br"));
40
+ probe.appendChild(ownerDocument.createTextNode("X"));
41
+ }
42
+ host.appendChild(probe);
43
+ try {
44
+ const probeHeight = probe.getBoundingClientRect().height;
45
+ if (probeHeight <= 0) return Number.NaN;
46
+ return probeHeight / sampleLines;
47
+ } catch {
48
+ return Number.NaN;
49
+ } finally {
50
+ if (probe.parentNode === host) {
51
+ host.removeChild(probe);
52
+ }
53
+ }
54
+ };
55
+ export {
56
+ getRenderedLineHeight
57
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -9,9 +9,10 @@ const getGravatarUrl = require("./get-gravatar-url.cjs.js");
9
9
  const getOrientation = require("./get-orientation.cjs.js");
10
10
  const getParentList = require("./get-parent-list.cjs.js");
11
11
  const getQueryParams = require("./get-query-params.cjs.js");
12
- const getRelativeTime = require("./get-relative-time.cjs.js");
13
12
  const getRandomInt = require("./get-random-int.cjs.js");
13
+ const getRelativeTime = require("./get-relative-time.cjs.js");
14
14
  const getRem = require("./get-rem.cjs.js");
15
+ const getRenderedLineHeight = require("./get-rendered-line-height.cjs.js");
15
16
  const getScrollbarWidth = require("./get-scrollbar-width.cjs.js");
16
17
  const getSessionStorage = require("./get-session-storage.cjs.js");
17
18
  const getStringLength = require("./get-string-length.cjs.js");
@@ -27,9 +28,10 @@ exports.getGravatarUrl = getGravatarUrl.getGravatarUrl;
27
28
  exports.getOrientation = getOrientation.getOrientation;
28
29
  exports.getParentList = getParentList.getParentList;
29
30
  exports.getQueryParams = getQueryParams.getQueryParams;
30
- exports.getRelativeTime = getRelativeTime.getRelativeTime;
31
31
  exports.getRandomInt = getRandomInt.getRandomInt;
32
+ exports.getRelativeTime = getRelativeTime.getRelativeTime;
32
33
  exports.getRem = getRem.getRem;
34
+ exports.getRenderedLineHeight = getRenderedLineHeight.getRenderedLineHeight;
33
35
  exports.getScrollbarWidth = getScrollbarWidth.getScrollbarWidth;
34
36
  exports.getSessionStorage = getSessionStorage.getSessionStorage;
35
37
  exports.getStringLength = getStringLength.getStringLength;
@@ -10,9 +10,10 @@ export * from './get-gravatar-url';
10
10
  export * from './get-orientation';
11
11
  export * from './get-parent-list';
12
12
  export * from './get-query-params';
13
- export * from './get-relative-time';
14
13
  export * from './get-random-int';
14
+ export * from './get-relative-time';
15
15
  export * from './get-rem';
16
+ export * from './get-rendered-line-height';
16
17
  export * from './get-scrollbar-width';
17
18
  export * from './get-session-storage';
18
19
  export * from './get-string-length';
@@ -7,9 +7,10 @@ import { getGravatarUrl } from "./get-gravatar-url.es.js";
7
7
  import { getOrientation } from "./get-orientation.es.js";
8
8
  import { getParentList } from "./get-parent-list.es.js";
9
9
  import { getQueryParams } from "./get-query-params.es.js";
10
- import { getRelativeTime } from "./get-relative-time.es.js";
11
10
  import { getRandomInt } from "./get-random-int.es.js";
11
+ import { getRelativeTime } from "./get-relative-time.es.js";
12
12
  import { getRem } from "./get-rem.es.js";
13
+ import { getRenderedLineHeight } from "./get-rendered-line-height.es.js";
13
14
  import { getScrollbarWidth } from "./get-scrollbar-width.es.js";
14
15
  import { getSessionStorage } from "./get-session-storage.es.js";
15
16
  import { getStringLength } from "./get-string-length.es.js";
@@ -29,6 +30,7 @@ export {
29
30
  getRandomInt,
30
31
  getRelativeTime,
31
32
  getRem,
33
+ getRenderedLineHeight,
32
34
  getScrollbarWidth,
33
35
  getSessionStorage,
34
36
  getStringLength,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "umaki",
3
- "version": "0.12.0",
3
+ "version": "0.13.0",
4
4
  "description": "A utility that is often used in web production.",
5
5
  "author": "TsubasaHiga",
6
6
  "homepage": "https://github.com/TsubasaHiga/umaki",