@julseb-lib/react 1.0.46 → 1.0.48

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/dist/index.d.cts CHANGED
@@ -532,28 +532,32 @@ declare const usePagination: ({ currentPage, setCurrentPage, totalPages, }: ILib
532
532
  };
533
533
 
534
534
  /**
535
- * Hook for calculating the number of lines text will occupy in a container with given dimensions and font properties.
535
+ * Hook for calculating the number of visual lines text will occupy in a textarea or input element based on actual rendered dimensions and styling.
536
536
  *
537
537
  * @hook
538
538
  *
539
539
  * @example
540
- * const lineCount = useTextLineCount(
541
- * "This is a long text that will wrap across multiple lines",
542
- * 300,
543
- * 16,
544
- * "Arial"
540
+ * const { visualLines, elementRef } = useTextLineCount("Long text content", 16)
541
+ *
542
+ * return (
543
+ * <textarea
544
+ * ref={elementRef}
545
+ * value={text}
546
+ * style={{ height: `${visualLines * 1.5}rem` }}
547
+ * />
545
548
  * )
546
549
  *
547
550
  * @param {string} text - The text content to measure for line count.
548
- * @param {number} containerWidth - The width of the container in pixels.
549
- * @param {number} [fontSize=16] - The font size in pixels. Default: 16.
550
- * @param {string} [fontFamily="lato"] - The font family to use for measurement. Default: "system-ui".
551
+ * @param {number} [fontSize=16] - The font size in pixels for fallback calculations. Default: 16.
551
552
  *
552
- * @returns {number} The number of lines the text will occupy.
553
+ * @returns {{ visualLines: number; elementRef: React.RefObject<HTMLTextAreaElement | HTMLInputElement> }} Object containing the calculated visual line count and element ref to attach to the target element.
553
554
  *
554
555
  * @see https://doc-julseb-lib-react.vercel.app/hooks/use-text-line-count
555
556
  */
556
- declare const useTextLineCount: (text: string, containerWidth: number, fontSize?: number, fontFamily?: string) => number;
557
+ declare const useTextLineCount: (text: string, fontSize?: number) => {
558
+ visualLines: number;
559
+ elementRef: react.RefObject<HTMLTextAreaElement | HTMLInputElement | null>;
560
+ };
557
561
 
558
562
  /**
559
563
  * Hook to detect if the current device supports touch screen interaction.
package/dist/index.d.ts CHANGED
@@ -532,28 +532,32 @@ declare const usePagination: ({ currentPage, setCurrentPage, totalPages, }: ILib
532
532
  };
533
533
 
534
534
  /**
535
- * Hook for calculating the number of lines text will occupy in a container with given dimensions and font properties.
535
+ * Hook for calculating the number of visual lines text will occupy in a textarea or input element based on actual rendered dimensions and styling.
536
536
  *
537
537
  * @hook
538
538
  *
539
539
  * @example
540
- * const lineCount = useTextLineCount(
541
- * "This is a long text that will wrap across multiple lines",
542
- * 300,
543
- * 16,
544
- * "Arial"
540
+ * const { visualLines, elementRef } = useTextLineCount("Long text content", 16)
541
+ *
542
+ * return (
543
+ * <textarea
544
+ * ref={elementRef}
545
+ * value={text}
546
+ * style={{ height: `${visualLines * 1.5}rem` }}
547
+ * />
545
548
  * )
546
549
  *
547
550
  * @param {string} text - The text content to measure for line count.
548
- * @param {number} containerWidth - The width of the container in pixels.
549
- * @param {number} [fontSize=16] - The font size in pixels. Default: 16.
550
- * @param {string} [fontFamily="lato"] - The font family to use for measurement. Default: "system-ui".
551
+ * @param {number} [fontSize=16] - The font size in pixels for fallback calculations. Default: 16.
551
552
  *
552
- * @returns {number} The number of lines the text will occupy.
553
+ * @returns {{ visualLines: number; elementRef: React.RefObject<HTMLTextAreaElement | HTMLInputElement> }} Object containing the calculated visual line count and element ref to attach to the target element.
553
554
  *
554
555
  * @see https://doc-julseb-lib-react.vercel.app/hooks/use-text-line-count
555
556
  */
556
- declare const useTextLineCount: (text: string, containerWidth: number, fontSize?: number, fontFamily?: string) => number;
557
+ declare const useTextLineCount: (text: string, fontSize?: number) => {
558
+ visualLines: number;
559
+ elementRef: react.RefObject<HTMLTextAreaElement | HTMLInputElement | null>;
560
+ };
557
561
 
558
562
  /**
559
563
  * Hook to detect if the current device supports touch screen interaction.
package/dist/index.js CHANGED
@@ -2312,48 +2312,75 @@ var usePagination = ({
2312
2312
 
2313
2313
  // src/lib/hooks/useTextLineCount.tsx
2314
2314
  import { useState as useState8, useRef, useCallback, useEffect as useEffect7 } from "react";
2315
- var useTextLineCount = (text, containerWidth, fontSize = 16, fontFamily = "lato") => {
2316
- const [lineCount, setLineCount] = useState8(1);
2317
- const canvasRef = useRef(null);
2315
+ var useTextLineCount = (text, fontSize = 16) => {
2316
+ const [visualLines, setVisualLines] = useState8(1);
2317
+ const elementRef = useRef(
2318
+ null
2319
+ );
2318
2320
  const measureLines = useCallback(() => {
2319
- if (!canvasRef.current) {
2320
- canvasRef.current = document.createElement("canvas");
2321
+ const element = elementRef.current;
2322
+ if (!element || !text) {
2323
+ setVisualLines(1);
2324
+ return;
2321
2325
  }
2322
- const canvas = canvasRef.current;
2323
- const context = canvas.getContext("2d");
2324
- if (!context || !text || containerWidth <= 0) {
2325
- setLineCount(1);
2326
+ const computedStyle = getComputedStyle(element);
2327
+ const paddingLeft = parseInt(computedStyle.paddingLeft) || 0;
2328
+ const paddingRight = parseInt(computedStyle.paddingRight) || 0;
2329
+ const borderLeft = parseInt(computedStyle.borderLeftWidth) || 0;
2330
+ const borderRight = parseInt(computedStyle.borderRightWidth) || 0;
2331
+ const actualWidth = element.offsetWidth - paddingLeft - paddingRight - borderLeft - borderRight;
2332
+ if (actualWidth <= 0) {
2333
+ setVisualLines(1);
2326
2334
  return;
2327
2335
  }
2328
- context.font = `${fontSize}px ${fontFamily}`;
2329
- const lines = text.split("\n");
2330
- let totalLines = 0;
2331
- lines.forEach((line) => {
2332
- if (line.length === 0) {
2333
- totalLines += 1;
2334
- return;
2335
- }
2336
- const words = line.split(" ");
2337
- let currentLine = "";
2338
- let linesForThisSegment = 1;
2339
- for (const word of words) {
2340
- const testLine = currentLine ? `${currentLine} ${word}` : word;
2341
- const testWidth = context.measureText(testLine).width;
2342
- if (testWidth > containerWidth && currentLine) {
2343
- linesForThisSegment++;
2344
- currentLine = word;
2345
- } else {
2346
- currentLine = testLine;
2347
- }
2348
- }
2349
- totalLines += linesForThisSegment;
2350
- });
2351
- setLineCount(Math.max(1, totalLines));
2352
- }, [text, containerWidth, fontSize, fontFamily]);
2336
+ try {
2337
+ const hiddenDiv = document.createElement("div");
2338
+ hiddenDiv.style.position = "absolute";
2339
+ hiddenDiv.style.visibility = "hidden";
2340
+ hiddenDiv.style.height = "auto";
2341
+ hiddenDiv.style.width = `${actualWidth}px`;
2342
+ hiddenDiv.style.fontSize = computedStyle.fontSize || `${fontSize}px`;
2343
+ hiddenDiv.style.fontFamily = computedStyle.fontFamily || "system-ui";
2344
+ hiddenDiv.style.lineHeight = computedStyle.lineHeight || "1.2";
2345
+ hiddenDiv.style.wordWrap = "break-word";
2346
+ hiddenDiv.style.whiteSpace = "pre-wrap";
2347
+ hiddenDiv.style.padding = "0";
2348
+ hiddenDiv.style.margin = "0";
2349
+ hiddenDiv.style.border = "none";
2350
+ document.body.appendChild(hiddenDiv);
2351
+ hiddenDiv.textContent = text;
2352
+ const elementHeight = hiddenDiv.offsetHeight;
2353
+ const lineHeight = parseInt(getComputedStyle(hiddenDiv).lineHeight) || fontSize * 1.2;
2354
+ const calculatedLines = Math.max(
2355
+ 1,
2356
+ Math.round(elementHeight / lineHeight)
2357
+ );
2358
+ document.body.removeChild(hiddenDiv);
2359
+ setVisualLines(calculatedLines);
2360
+ } catch (error) {
2361
+ console.warn("Element line count measurement failed:", error);
2362
+ setVisualLines(text.split("\n").length);
2363
+ }
2364
+ }, [text, fontSize]);
2353
2365
  useEffect7(() => {
2354
- measureLines();
2366
+ const timer = setTimeout(measureLines, 50);
2367
+ return () => clearTimeout(timer);
2355
2368
  }, [measureLines]);
2356
- return lineCount;
2369
+ useEffect7(() => {
2370
+ const element = elementRef.current;
2371
+ if (!element) return;
2372
+ const resizeObserver = new ResizeObserver(() => {
2373
+ measureLines();
2374
+ });
2375
+ resizeObserver.observe(element);
2376
+ return () => {
2377
+ resizeObserver.disconnect();
2378
+ };
2379
+ }, [measureLines]);
2380
+ return {
2381
+ visualLines,
2382
+ elementRef
2383
+ };
2357
2384
  };
2358
2385
 
2359
2386
  // src/lib/hooks/useTouchScreen.tsx
@@ -69795,9 +69822,9 @@ var PageLayout = ({
69795
69822
  }) => {
69796
69823
  return /* @__PURE__ */ jsxs295(Fragment16, { children: [
69797
69824
  meta && /* @__PURE__ */ jsx369(Meta, { ...meta }),
69798
- isValidElement(header) ? header : /* @__PURE__ */ jsx369(Header, { ...header }),
69825
+ header && (isValidElement(header) ? header : /* @__PURE__ */ jsx369(Header, { ...header })),
69799
69826
  !noWrapper ? /* @__PURE__ */ jsx369(Wrapper, { ...wrapperProps, children: !noMain ? /* @__PURE__ */ jsx369(Main, { ...mainProps, children }) : children }) : children,
69800
- isValidElement(footer) ? footer : /* @__PURE__ */ jsx369(Footer, { ...footer })
69827
+ footer && (isValidElement(footer) ? footer : /* @__PURE__ */ jsx369(Footer, { ...footer }))
69801
69828
  ] });
69802
69829
  };
69803
69830