@julseb-lib/react 1.0.45 → 1.0.47

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
@@ -531,7 +531,33 @@ declare const usePagination: ({ currentPage, setCurrentPage, totalPages, }: ILib
531
531
  handlePage: (n: number) => void;
532
532
  };
533
533
 
534
- declare const useTextLineCount: (text: string, containerWidth: number, fontSize?: number, fontFamily?: string) => number;
534
+ /**
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
+ *
537
+ * @hook
538
+ *
539
+ * @example
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
+ * />
548
+ * )
549
+ *
550
+ * @param {string} text - The text content to measure for line count.
551
+ * @param {number} [fontSize=16] - The font size in pixels for fallback calculations. Default: 16.
552
+ *
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.
554
+ *
555
+ * @see https://doc-julseb-lib-react.vercel.app/hooks/use-text-line-count
556
+ */
557
+ declare const useTextLineCount: (text: string, fontSize?: number) => {
558
+ visualLines: number;
559
+ elementRef: react.RefObject<HTMLTextAreaElement | HTMLInputElement | null>;
560
+ };
535
561
 
536
562
  /**
537
563
  * Hook to detect if the current device supports touch screen interaction.
package/dist/index.d.ts CHANGED
@@ -531,7 +531,33 @@ declare const usePagination: ({ currentPage, setCurrentPage, totalPages, }: ILib
531
531
  handlePage: (n: number) => void;
532
532
  };
533
533
 
534
- declare const useTextLineCount: (text: string, containerWidth: number, fontSize?: number, fontFamily?: string) => number;
534
+ /**
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
+ *
537
+ * @hook
538
+ *
539
+ * @example
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
+ * />
548
+ * )
549
+ *
550
+ * @param {string} text - The text content to measure for line count.
551
+ * @param {number} [fontSize=16] - The font size in pixels for fallback calculations. Default: 16.
552
+ *
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.
554
+ *
555
+ * @see https://doc-julseb-lib-react.vercel.app/hooks/use-text-line-count
556
+ */
557
+ declare const useTextLineCount: (text: string, fontSize?: number) => {
558
+ visualLines: number;
559
+ elementRef: react.RefObject<HTMLTextAreaElement | HTMLInputElement | null>;
560
+ };
535
561
 
536
562
  /**
537
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 = "system-ui") => {
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