@gridui/input 3.0.1 → 4.0.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
@@ -56,3 +56,9 @@ Fetching, debouncing, caching and form submission remain application-owned.
56
56
  For both compositions, ref, className, style, name and native attributes target
57
57
  the input; `rootClassName` styles the group. Standard and small densities remain
58
58
  available. `Field` and `useField` from Form can associate labels and help text.
59
+
60
+ Textarea's `resizeHandle` selects `edge` (default), `corner`, `footer`, or `false`.
61
+ Automatic sizing hides the handle. Its optional `footer` slot composes counters
62
+ or actions. Unlike the single-line compositions above, Textarea's className/style
63
+ style its structural frame; the ref and native attributes still target the native
64
+ textarea. See the [Textarea migration contract](docs/textarea.md#frame-styling-and-migration).
package/dist/index.js CHANGED
@@ -305,11 +305,242 @@ var SearchInput = (props) => {
305
305
  );
306
306
  };
307
307
 
308
+ // src/textarea/resize-handle.tsx
309
+ import { GripHorizontal, MoveVertical } from "lucide-react";
310
+ import { jsx as jsx8 } from "react/jsx-runtime";
311
+ var ResizeHandle = (props) => {
312
+ const { variant, label, controlId, dragging, ...restProps } = props;
313
+ const Icon = variant === "edge" ? GripHorizontal : MoveVertical;
314
+ return /* @__PURE__ */ jsx8(
315
+ "button",
316
+ {
317
+ ...restProps,
318
+ type: "button",
319
+ className: "gridui-textarea-resize-handle",
320
+ "data-variant": variant,
321
+ "data-dragging": dragging || void 0,
322
+ "aria-label": label,
323
+ "aria-controls": controlId,
324
+ "aria-keyshortcuts": "ArrowUp ArrowDown Home End Enter Space",
325
+ children: /* @__PURE__ */ jsx8(
326
+ Icon,
327
+ {
328
+ size: 16,
329
+ "aria-hidden": "true"
330
+ }
331
+ )
332
+ }
333
+ );
334
+ };
335
+
336
+ // src/textarea/use-textarea-footer-height.ts
337
+ import { useLayoutEffect, useRef as useRef2, useState as useState2 } from "react";
338
+ var useTextareaFooterHeight = (visible) => {
339
+ const ref = useRef2(null);
340
+ const [
341
+ height,
342
+ setHeight
343
+ ] = useState2();
344
+ useLayoutEffect(() => {
345
+ const footer = ref.current;
346
+ if (!visible || !footer) {
347
+ return;
348
+ }
349
+ const measure = () => {
350
+ setHeight(parseFloat(footer.ownerDocument.defaultView.getComputedStyle(footer).height));
351
+ };
352
+ measure();
353
+ const observer = new ResizeObserver(measure);
354
+ observer.observe(footer);
355
+ return () => {
356
+ observer.disconnect();
357
+ };
358
+ }, [visible]);
359
+ return {
360
+ ref,
361
+ height: visible ? height : void 0
362
+ };
363
+ };
364
+
365
+ // src/textarea/use-textarea-resize.ts
366
+ import { isComposingKeyEvent, listenForDragCancellation } from "@gridui/interaction";
367
+ import { useCallback, useEffect, useLayoutEffect as useLayoutEffect2, useRef as useRef3, useState as useState3 } from "react";
368
+ var getResizeGeometry = (frame) => {
369
+ const style = frame.ownerDocument.defaultView.getComputedStyle(frame);
370
+ return {
371
+ height: parseFloat(style.height),
372
+ maximum: style.maxHeight === "none" ? void 0 : style.maxHeight,
373
+ scale: frame.getBoundingClientRect().height / frame.offsetHeight || 1
374
+ };
375
+ };
376
+ var releaseDrag = (drag) => {
377
+ drag.cleanup();
378
+ if (drag.handle.hasPointerCapture(drag.pointerId)) {
379
+ drag.handle.releasePointerCapture(drag.pointerId);
380
+ }
381
+ };
382
+ var useTextareaResize = ({ frame, input, enabled, disabled }) => {
383
+ const [
384
+ height,
385
+ setHeight
386
+ ] = useState3();
387
+ const [
388
+ dragging,
389
+ setDragging
390
+ ] = useState3(false);
391
+ const drag = useRef3(null);
392
+ const blocked = () => {
393
+ return !enabled || disabled || !input.current || input.current.matches(":disabled") || input.current.inert;
394
+ };
395
+ const finish = (restore) => {
396
+ const active = drag.current;
397
+ if (!active) {
398
+ return;
399
+ }
400
+ drag.current = null;
401
+ releaseDrag(active);
402
+ if (restore) {
403
+ setHeight(active.previousHeight);
404
+ }
405
+ setDragging(false);
406
+ };
407
+ const cancel = useRef3(() => {
408
+ return;
409
+ });
410
+ useLayoutEffect2(() => {
411
+ cancel.current = () => {
412
+ finish(true);
413
+ };
414
+ if (blocked()) {
415
+ finish(true);
416
+ }
417
+ });
418
+ useLayoutEffect2(() => {
419
+ if (!enabled) {
420
+ setHeight(void 0);
421
+ }
422
+ }, [enabled]);
423
+ useEffect(() => {
424
+ return () => {
425
+ cancel.current();
426
+ };
427
+ }, []);
428
+ const handleRef = useCallback((handle) => {
429
+ if (!handle) {
430
+ return;
431
+ }
432
+ return () => {
433
+ if (handle.ownerDocument.activeElement === handle) {
434
+ input.current?.focus({ preventScroll: true });
435
+ }
436
+ };
437
+ }, [input]);
438
+ const update = (next) => {
439
+ if (blocked() || !frame.current) {
440
+ finish(true);
441
+ return;
442
+ }
443
+ setHeight(typeof next === "number" ? Math.max(0, next) : next);
444
+ };
445
+ const reset = () => {
446
+ if (!blocked()) {
447
+ finish(false);
448
+ setHeight(void 0);
449
+ }
450
+ };
451
+ const handleProps = {
452
+ ref: handleRef,
453
+ onPointerDown: (event) => {
454
+ if (blocked() || event.button !== 0 || !event.isPrimary || drag.current || !frame.current) {
455
+ return;
456
+ }
457
+ const bounds = getResizeGeometry(frame.current);
458
+ const handle = event.currentTarget;
459
+ event.preventDefault();
460
+ handle.focus({ preventScroll: true });
461
+ drag.current = {
462
+ pointerId: event.pointerId,
463
+ startY: event.clientY,
464
+ startHeight: bounds.height,
465
+ previousHeight: height,
466
+ scale: bounds.scale,
467
+ handle,
468
+ cleanup: listenForDragCancellation(handle, () => {
469
+ cancel.current();
470
+ })
471
+ };
472
+ handle.setPointerCapture(event.pointerId);
473
+ setDragging(true);
474
+ },
475
+ onPointerMove: (event) => {
476
+ const active = drag.current;
477
+ if (active?.pointerId === event.pointerId) {
478
+ update(active.startHeight + (event.clientY - active.startY) / active.scale);
479
+ }
480
+ },
481
+ onPointerUp: (event) => {
482
+ if (drag.current?.pointerId === event.pointerId) {
483
+ finish(blocked());
484
+ }
485
+ },
486
+ onPointerCancel: (event) => {
487
+ if (drag.current?.pointerId === event.pointerId) {
488
+ finish(true);
489
+ }
490
+ },
491
+ onLostPointerCapture: (event) => {
492
+ if (drag.current?.pointerId === event.pointerId) {
493
+ finish(true);
494
+ }
495
+ },
496
+ onKeyDown: (event) => {
497
+ if (blocked() || drag.current || !frame.current || isComposingKeyEvent(event.nativeEvent)) {
498
+ return;
499
+ }
500
+ const bounds = getResizeGeometry(frame.current);
501
+ const step = event.shiftKey ? 64 : 16;
502
+ let next;
503
+ switch (event.key) {
504
+ case "ArrowUp":
505
+ next = bounds.height - step;
506
+ break;
507
+ case "ArrowDown":
508
+ next = bounds.height + step;
509
+ break;
510
+ case "Home":
511
+ next = 0;
512
+ break;
513
+ case "End":
514
+ if (bounds.maximum === void 0) {
515
+ return;
516
+ }
517
+ next = bounds.maximum;
518
+ break;
519
+ default:
520
+ return;
521
+ }
522
+ event.preventDefault();
523
+ update(next);
524
+ },
525
+ onClick: (event) => {
526
+ if (event.detail === 0) {
527
+ reset();
528
+ }
529
+ },
530
+ onDoubleClick: reset
531
+ };
532
+ return {
533
+ height,
534
+ dragging,
535
+ handleProps
536
+ };
537
+ };
538
+
308
539
  // src/textarea/textarea.tsx
309
540
  import { useScrollbars } from "@gridui/overlay";
310
541
  import { cn as cn8 } from "@gridui/style-utils";
311
- import "react";
312
- import { jsx as jsx8 } from "react/jsx-runtime";
542
+ import { useId, useImperativeHandle as useImperativeHandle2, useRef as useRef4 } from "react";
543
+ import { jsx as jsx9, jsxs as jsxs4 } from "react/jsx-runtime";
313
544
  var Textarea = (props) => {
314
545
  const groupSize = useInputGroupSize();
315
546
  const {
@@ -323,6 +554,9 @@ var Textarea = (props) => {
323
554
  maxRows,
324
555
  rows = 2,
325
556
  style,
557
+ resizeHandle = "edge",
558
+ resizeLabel = "Resize text area",
559
+ footer,
326
560
  ...restProps
327
561
  } = props;
328
562
  if (maxHeight !== void 0 && (!Number.isFinite(maxHeight) || maxHeight <= 0)) {
@@ -334,27 +568,100 @@ var Textarea = (props) => {
334
568
  if (maxHeight !== void 0 && maxRows !== void 0) {
335
569
  throw new RangeError("Textarea accepts either maxRows or maxHeight, not both.");
336
570
  }
337
- const scrollRef = useScrollbars({ ref });
571
+ const frame = useRef4(null);
572
+ const input = useRef4(null);
573
+ const generatedId = useId();
574
+ const inputId = restProps.id ?? generatedId;
575
+ useImperativeHandle2(ref, () => {
576
+ return input.current;
577
+ }, []);
578
+ const scrollRef = useScrollbars({ ref: input });
579
+ const manual = !autoSize && resizeHandle !== false;
580
+ const resize = useTextareaResize({
581
+ frame,
582
+ input,
583
+ enabled: manual,
584
+ disabled: Boolean(restProps.disabled)
585
+ });
586
+ const hasFooter = footer !== void 0 && footer !== null && typeof footer !== "boolean";
587
+ const showFooter = hasFooter || manual && resizeHandle === "footer";
588
+ const footerLayout = useTextareaFooterHeight(showFooter);
589
+ const handle = manual ? /* @__PURE__ */ jsx9(
590
+ ResizeHandle,
591
+ {
592
+ ...resize.handleProps,
593
+ variant: resizeHandle,
594
+ label: resizeLabel,
595
+ controlId: inputId,
596
+ dragging: resize.dragging,
597
+ disabled: restProps.disabled
598
+ }
599
+ ) : null;
338
600
  const sizing = {
339
601
  "--_gridui-textarea-rows": rows,
340
602
  "--_gridui-textarea-max-rows": maxRows,
603
+ "--_gridui-textarea-footer-height": footerLayout.height === void 0 ? void 0 : `${footerLayout.height}px`,
341
604
  ...style,
342
- ...autoSize && maxHeight !== void 0 ? { maxHeight } : {}
605
+ ...maxHeight !== void 0 ? { maxHeight } : {},
606
+ ...manual && resize.height !== void 0 ? { height: resize.height } : {}
343
607
  };
344
- return /* @__PURE__ */ jsx8(
345
- "textarea",
608
+ return /* @__PURE__ */ jsxs4(
609
+ "div",
346
610
  {
347
- ...restProps,
348
- ref: scrollRef,
349
- rows,
611
+ ref: frame,
612
+ dir: restProps.dir,
613
+ lang: restProps.lang,
614
+ hidden: restProps.hidden,
615
+ inert: restProps.inert,
350
616
  style: sizing,
617
+ className: cn8(controlStyles(), "gridui-textarea-frame", restProps["aria-invalid"] && restProps["aria-invalid"] !== "false" && "gridui-u-input--border-danger-foreground", groupSize && "gridui-u-input--border-0 gridui-u-input--border-inherit gridui-u-input--bg-transparent", className),
618
+ "data-gridui-outline-target": groupSize === null ? "" : void 0,
619
+ "data-control-size": controlSize,
620
+ "data-grouped": groupSize !== null || void 0,
351
621
  "data-autosize": autoSize || void 0,
352
622
  "data-max-rows": maxRows !== void 0 || void 0,
353
- onChange: (event) => {
354
- onChange?.(event);
355
- onValueChange?.(event.currentTarget.value);
356
- },
357
- className: cn8(controlVariants({ size: controlSize }), "gridui-textarea gridui-u-input--overflow-auto", groupSize && "gridui-u-input--border-0 gridui-u-input--bg-transparent", autoSize ? "gridui-u-input--resize-none gridui-u-input--min-h-(--_gridui-textarea-min-height)" : "gridui-u-input--resize-y", className)
623
+ "data-resize-handle": manual ? resizeHandle : void 0,
624
+ "data-footer": hasFooter || void 0,
625
+ children: [
626
+ /* @__PURE__ */ jsx9(
627
+ "textarea",
628
+ {
629
+ ...restProps,
630
+ id: inputId,
631
+ ref: scrollRef,
632
+ rows,
633
+ onChange: (event) => {
634
+ onChange?.(event);
635
+ onValueChange?.(event.currentTarget.value);
636
+ },
637
+ className: "gridui-textarea"
638
+ }
639
+ ),
640
+ manual && resizeHandle !== "footer" && /* @__PURE__ */ jsx9(
641
+ "div",
642
+ {
643
+ className: "gridui-textarea-resize-row",
644
+ children: handle
645
+ }
646
+ ),
647
+ showFooter && /* @__PURE__ */ jsxs4(
648
+ "div",
649
+ {
650
+ ref: footerLayout.ref,
651
+ className: "gridui-textarea-footer",
652
+ children: [
653
+ /* @__PURE__ */ jsx9(
654
+ "div",
655
+ {
656
+ className: "gridui-textarea-footer-content",
657
+ children: footer
658
+ }
659
+ ),
660
+ resizeHandle === "footer" && handle
661
+ ]
662
+ }
663
+ )
664
+ ]
358
665
  }
359
666
  );
360
667
  };
package/dist/index.js.map CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
- "sources": ["../src/shared/input-group-context.ts", "../src/shared/style.ts", "../src/input/input.tsx", "../src/input-group/input-group.tsx", "../src/input-group-addon/input-group-addon.tsx", "../src/input-group-button/input-group-button.tsx", "../src/label/style.ts", "../src/label/label.tsx", "../src/password-input/password-input.tsx", "../src/search-input/search-input.tsx", "../src/textarea/textarea.tsx"],
4
- "sourcesContent": ["/**\n * @author WMXPY\n * @package Input\n * @namespace Shared\n * @description Input Group Context\n */\n\nimport { createContext, useContext } from \"react\";\n\nexport const inputGroupContext = createContext<\"standard\" | \"small\" | null>(null);\nexport const useInputGroupSize = () => {\n return useContext(inputGroupContext);\n};\n", "/**\n * @author WMXPY\n * @package Input\n * @namespace Shared\n * @description Style\n */\n\nimport { cn } from \"@gridui/style-utils\";\nimport { cva } from \"class-variance-authority\";\n\nexport const controlStyles = cva(cn(\n \"gridui-control\",\n \"gridui-u-input--min-w-0 gridui-u-input--w-full gridui-u-input--box-border gridui-u-input--border gridui-u-input--border-solid gridui-u-input--border-border gridui-u-input--rounded-control gridui-u-input--text-foreground gridui-u-input--bg-background gridui-u-input--type-input\",\n \"gridui-u-input--disabled:opacity-50 gridui-u-input--disabled:cursor-not-allowed\",\n \"gridui-u-input--aria-invalid:border-danger-foreground\",\n));\n\nexport const controlStandardStyles = cva(cn(\n \"gridui-control-standard\",\n \"gridui-u-input--p-gridui-8 gridui-u-input--min-h-gridui-control\",\n));\n\nexport const controlSmallStyles = cva(cn(\n \"gridui-control-small\",\n \"gridui-u-input--py-gridui-4 gridui-u-input--px-gridui-8 gridui-u-input--min-h-gridui-control-small\",\n));\n\nexport const controlVariants = cva(controlStyles(), {\n variants: {\n size: {\n standard: controlStandardStyles(),\n small: controlSmallStyles(),\n },\n },\n defaultVariants: { size: \"standard\" },\n});\n", "/**\n * @author WMXPY\n * @package Input\n * @namespace Input\n * @description Input\n */\n\nimport { useInputGroupSize } from \"../shared/input-group-context\";\nimport { controlVariants } from \"../shared/style\";\nimport type { TextControlOptions } from \"../shared/text-control-options\";\nimport { cn } from \"@gridui/style-utils\";\nimport type { ComponentPropsWithRef, FC } from \"react\";\n\nexport type InputProps = ComponentPropsWithRef<\"input\"> & TextControlOptions;\n\nexport const Input: FC<InputProps> = (props: InputProps) => {\n const groupSize = useInputGroupSize();\n const {\n size,\n controlSize = groupSize ?? \"standard\",\n className,\n onChange,\n onValueChange,\n ...restProps\n } = props;\n return (<input\n {...restProps}\n size={size}\n onChange={(event) => {\n onChange?.(event);\n onValueChange?.(event.currentTarget.value);\n }}\n className={cn(controlVariants({ size: controlSize }), groupSize && \"gridui-u-input--flex-1 gridui-u-input--w-0 gridui-u-input--border-0 gridui-u-input--bg-transparent\", className)}\n />);\n};\n", "/**\n * @author WMXPY\n * @package Input\n * @namespace InputGroup\n * @description Input Group\n */\n\nimport { inputGroupContext } from \"../shared/input-group-context\";\nimport { cn } from \"@gridui/style-utils\";\nimport type { ComponentPropsWithRef, FC } from \"react\";\n\nexport type InputGroupProps = ComponentPropsWithRef<\"div\"> & {\n readonly controlSize?: \"standard\" | \"small\";\n};\nexport const InputGroup: FC<InputGroupProps> = (props: InputGroupProps) => {\n const { controlSize = \"standard\", className, children, ...restProps } = props;\n return (<inputGroupContext.Provider\n value={controlSize}\n >\n <div\n {...restProps}\n data-control-size={controlSize}\n data-gridui-outline-target=\"\"\n className={cn(\"gridui-input-group\", className)}\n >\n {children}\n </div>\n </inputGroupContext.Provider>);\n};\n", "/**\n * @author WMXPY\n * @package Input\n * @namespace InputGroupAddon\n * @description Input Group Addon\n */\n\nimport { cn } from \"@gridui/style-utils\";\nimport type { ComponentPropsWithRef, FC } from \"react\";\n\nexport type InputGroupAddonProps = ComponentPropsWithRef<\"div\"> & {\n readonly align?: \"inline-start\" | \"inline-end\" | \"block-start\" | \"block-end\";\n};\nexport const InputGroupAddon: FC<InputGroupAddonProps> = (props: InputGroupAddonProps) => {\n const { align = \"inline-start\", className, ...restProps } = props;\n return (<div\n {...restProps}\n data-align={align}\n className={cn(\"gridui-input-group-addon\", className)}\n />);\n};\n", "/**\n * @author WMXPY\n * @package Input\n * @namespace InputGroupButton\n * @description Input Group Button\n */\n\nimport { useInputGroupSize } from \"../shared/input-group-context\";\nimport { Button, type ButtonProps } from \"@gridui/button\";\nimport { cn } from \"@gridui/style-utils\";\nimport type { FC } from \"react\";\n\nexport type InputGroupButtonProps = ButtonProps;\nexport const InputGroupButton: FC<InputGroupButtonProps> = (props: InputGroupButtonProps) => {\n const size = useInputGroupSize() ?? \"standard\";\n const { className, ...restProps } = props;\n return (<Button\n variant=\"quiet\"\n size={size}\n {...restProps}\n className={cn(\"gridui-input-group-button\", className)}\n />);\n};\n", "/**\n * @author WMXPY\n * @package Input\n * @namespace Label\n * @description Style\n */\n\nimport { cn } from \"@gridui/style-utils\";\nimport { cva } from \"class-variance-authority\";\n\nexport const labelStyles = cva(cn(\n \"gridui-label\",\n \"gridui-u-input--inline-flex gridui-u-input--items-center gridui-u-input--gap-gridui-4 gridui-u-input--type-ui\",\n));\n\nexport const labelIconStyles = cva(\"gridui-u-input--inline-flex gridui-u-input--shrink-0 gridui-u-input--text-foreground gridui-u-input--[&>svg]:size-gridui-16\");\n", "/**\n * @author WMXPY\n * @package Input\n * @namespace Label\n * @description Label\n */\n\nimport { labelIconStyles, labelStyles } from \"./style\";\nimport { cn } from \"@gridui/style-utils\";\nimport type { ComponentPropsWithRef, FC, ReactNode } from \"react\";\n\nexport type LabelProps = ComponentPropsWithRef<\"label\"> & {\n readonly icon?: ReactNode;\n};\n\nexport const Label: FC<LabelProps> = (props: LabelProps) => {\n const { icon, children, className, ...restProps } = props;\n return (<label\n {...restProps}\n className={cn(labelStyles(), className)}\n >\n {icon && (<span\n aria-hidden=\"true\"\n className={labelIconStyles()}\n >\n {icon}\n </span>)}\n {children}\n </label>);\n};\n", "/**\n * @author WMXPY\n * @package Input\n * @namespace PasswordInput\n * @description Password Input\n */\n\nimport { Input, type InputProps } from \"../input/input\";\nimport { InputGroup } from \"../input-group/input-group\";\nimport { InputGroupAddon } from \"../input-group-addon/input-group-addon\";\nimport { InputGroupButton } from \"../input-group-button/input-group-button\";\nimport { Eye, EyeOff } from \"lucide-react\";\nimport { type FC, useState } from \"react\";\n\nexport type PasswordInputProps = Omit<InputProps, \"type\"> & {\n readonly showLabel: string;\n readonly hideLabel: string;\n readonly rootClassName?: string;\n};\nexport const PasswordInput: FC<PasswordInputProps> = (props: PasswordInputProps) => {\n const { showLabel, hideLabel, rootClassName, controlSize = \"standard\", ...restProps } = props;\n const [\n visible,\n setVisible,\n ] = useState(false);\n return (<InputGroup\n controlSize={controlSize}\n dir={restProps.dir}\n className={rootClassName}\n >\n <Input\n {...restProps}\n type={visible ? \"text\" : \"password\"}\n />\n <InputGroupAddon\n align=\"inline-end\"\n >\n <InputGroupButton\n layout=\"icon\"\n aria-label={visible ? hideLabel : showLabel}\n disabled={restProps.disabled}\n onClick={() => {\n setVisible(current => {\n return !current;\n });\n }}\n >\n {visible ? (<EyeOff\n aria-hidden=\"true\"\n />) : (<Eye\n aria-hidden=\"true\"\n />)}\n </InputGroupButton>\n </InputGroupAddon>\n </InputGroup>);\n};\n", "/**\n * @author WMXPY\n * @package Input\n * @namespace SearchInput\n * @description Search Input\n */\n\nimport { Input, type InputProps } from \"../input/input\";\nimport { InputGroup } from \"../input-group/input-group\";\nimport { InputGroupAddon } from \"../input-group-addon/input-group-addon\";\nimport { InputGroupButton } from \"../input-group-button/input-group-button\";\nimport { Search, X } from \"lucide-react\";\nimport { type FC, useImperativeHandle, useRef } from \"react\";\n\nexport type SearchInputProps = Omit<InputProps, \"type\"> & {\n readonly clearLabel: string;\n readonly loading?: boolean;\n readonly loadingLabel?: string;\n readonly rootClassName?: string;\n};\nexport const SearchInput: FC<SearchInputProps> = (props: SearchInputProps) => {\n const { clearLabel, loading = false, loadingLabel, rootClassName, controlSize = \"standard\", ref,\n onValueChange, ...restProps } = props;\n const input = useRef<HTMLInputElement>(null);\n useImperativeHandle(ref, () => {\n return input.current!;\n });\n return (<InputGroup\n controlSize={controlSize}\n dir={restProps.dir}\n className={rootClassName}\n >\n <Input\n {...restProps}\n ref={input}\n type=\"search\"\n aria-busy={loading || restProps[\"aria-busy\"]}\n onValueChange={(next) => {\n onValueChange?.(next);\n }}\n />\n <InputGroupAddon\n aria-hidden=\"true\"\n >\n <Search\n size={16}\n />\n </InputGroupAddon>\n <InputGroupAddon\n align=\"inline-end\"\n >\n {loading && (<span\n role=\"status\"\n >\n {loadingLabel}\n </span>)}\n <InputGroupButton\n layout=\"icon\"\n aria-label={clearLabel}\n disabled={restProps.disabled || restProps.readOnly}\n onClick={() => {\n const element = input.current;\n if (!element || element.matches(\":disabled\") || element.readOnly) {\n return;\n }\n if (element.value !== \"\") {\n if (restProps.value === undefined) {\n element.value = \"\";\n }\n onValueChange?.(\"\");\n }\n element.focus();\n }}\n >\n <X\n aria-hidden=\"true\"\n />\n </InputGroupButton>\n </InputGroupAddon>\n </InputGroup>);\n};\n", "/**\n * @author WMXPY\n * @package Input\n * @namespace Textarea\n * @description Textarea\n */\n\nimport { useInputGroupSize } from \"../shared/input-group-context\";\nimport { controlVariants } from \"../shared/style\";\nimport type { TextControlOptions } from \"../shared/text-control-options\";\nimport { useScrollbars } from \"@gridui/overlay\";\nimport { cn } from \"@gridui/style-utils\";\nimport { type ComponentPropsWithRef, type CSSProperties, type FC } from \"react\";\n\nexport type TextareaProps = ComponentPropsWithRef<\"textarea\"> & TextControlOptions & {\n readonly autoSize?: boolean;\n readonly maxHeight?: number;\n readonly maxRows?: number;\n};\nexport const Textarea: FC<TextareaProps> = (props: TextareaProps) => {\n const groupSize = useInputGroupSize();\n const { controlSize = groupSize ?? \"standard\", className, onChange, onValueChange, ref, autoSize = false,\n maxHeight, maxRows, rows = 2, style, ...restProps } = props;\n if (maxHeight !== undefined && (!Number.isFinite(maxHeight) || maxHeight <= 0)) {\n throw new RangeError(\"Textarea maxHeight must be finite and positive.\");\n }\n if (maxRows !== undefined && (!Number.isSafeInteger(maxRows) || maxRows < rows)) {\n throw new RangeError(\"Textarea maxRows must be an integer at least as large as rows.\");\n }\n if (maxHeight !== undefined && maxRows !== undefined) {\n throw new RangeError(\"Textarea accepts either maxRows or maxHeight, not both.\");\n }\n const scrollRef = useScrollbars<HTMLTextAreaElement>({ ref });\n const sizing: CSSProperties & Record<`--${string}`, string | number | undefined> = {\n \"--_gridui-textarea-rows\": rows,\n \"--_gridui-textarea-max-rows\": maxRows,\n ...style,\n ...(autoSize && maxHeight !== undefined ? { maxHeight } : {}),\n };\n return (<textarea\n {...restProps}\n ref={scrollRef}\n rows={rows}\n style={sizing}\n data-autosize={autoSize || undefined}\n data-max-rows={maxRows !== undefined || undefined}\n onChange={(event) => {\n onChange?.(event);\n onValueChange?.(event.currentTarget.value);\n }}\n className={cn(controlVariants({ size: controlSize }), \"gridui-textarea gridui-u-input--overflow-auto\", groupSize && \"gridui-u-input--border-0 gridui-u-input--bg-transparent\", autoSize ? \"gridui-u-input--resize-none gridui-u-input--min-h-(--_gridui-textarea-min-height)\" : \"gridui-u-input--resize-y\", className)}\n />);\n};\n"],
5
- "mappings": ";AAOA,SAAS,eAAe,kBAAkB;AAEnC,IAAM,oBAAoB,cAA2C,IAAI;AACzE,IAAM,oBAAoB,MAAM;AACnC,SAAO,WAAW,iBAAiB;AACvC;;;ACLA,SAAS,UAAU;AACnB,SAAS,WAAW;AAEb,IAAM,gBAAgB,IAAI;AAAA,EAC7B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACJ,CAAC;AAEM,IAAM,wBAAwB,IAAI;AAAA,EACrC;AAAA,EACA;AACJ,CAAC;AAEM,IAAM,qBAAqB,IAAI;AAAA,EAClC;AAAA,EACA;AACJ,CAAC;AAEM,IAAM,kBAAkB,IAAI,cAAc,GAAG;AAAA,EAChD,UAAU;AAAA,IACN,MAAM;AAAA,MACF,UAAU,sBAAsB;AAAA,MAChC,OAAO,mBAAmB;AAAA,IAC9B;AAAA,EACJ;AAAA,EACA,iBAAiB,EAAE,MAAM,WAAW;AACxC,CAAC;;;ACzBD,SAAS,MAAAA,WAAU;AAeP;AAVL,IAAM,QAAwB,CAAC,UAAsB;AACxD,QAAM,YAAY,kBAAkB;AACpC,QAAM;AAAA,IACF;AAAA,IACA,cAAc,aAAa;AAAA,IAC3B;AAAA,IACA;AAAA,IACA;AAAA,IACA,GAAG;AAAA,EACP,IAAI;AACJ,SAAQ;AAAA,IAAC;AAAA;AAAA,MACJ,GAAG;AAAA,MACJ;AAAA,MACA,UAAU,CAAC,UAAU;AACjB,mBAAW,KAAK;AAChB,wBAAgB,MAAM,cAAc,KAAK;AAAA,MAC7C;AAAA,MACA,WAAWA,IAAG,gBAAgB,EAAE,MAAM,YAAY,CAAC,GAAG,aAAa,sGAAsG,SAAS;AAAA;AAAA,EACtL;AACJ;;;AC1BA,SAAS,MAAAC,WAAU;AAWX,gBAAAC,YAAA;AALD,IAAM,aAAkC,CAAC,UAA2B;AACvE,QAAM,EAAE,cAAc,YAAY,WAAW,UAAU,GAAG,UAAU,IAAI;AACxE,SAAQ,gBAAAA;AAAA,IAAC,kBAAkB;AAAA,IAAlB;AAAA,MACL,OAAO;AAAA,MAEP,0BAAAA;AAAA,QAAC;AAAA;AAAA,UACI,GAAG;AAAA,UACJ,qBAAmB;AAAA,UACnB,8BAA2B;AAAA,UAC3B,WAAWD,IAAG,sBAAsB,SAAS;AAAA,UAE5C;AAAA;AAAA,MACL;AAAA;AAAA,EACJ;AACJ;;;ACrBA,SAAS,MAAAE,WAAU;AAQP,gBAAAC,YAAA;AAFL,IAAM,kBAA4C,CAAC,UAAgC;AACtF,QAAM,EAAE,QAAQ,gBAAgB,WAAW,GAAG,UAAU,IAAI;AAC5D,SAAQ,gBAAAA;AAAA,IAAC;AAAA;AAAA,MACJ,GAAG;AAAA,MACJ,cAAY;AAAA,MACZ,WAAWD,IAAG,4BAA4B,SAAS;AAAA;AAAA,EACvD;AACJ;;;ACZA,SAAS,cAAgC;AACzC,SAAS,MAAAE,WAAU;AAOP,gBAAAC,YAAA;AAHL,IAAM,mBAA8C,CAAC,UAAiC;AACzF,QAAM,OAAO,kBAAkB,KAAK;AACpC,QAAM,EAAE,WAAW,GAAG,UAAU,IAAI;AACpC,SAAQ,gBAAAA;AAAA,IAAC;AAAA;AAAA,MACL,SAAQ;AAAA,MACR;AAAA,MACC,GAAG;AAAA,MACJ,WAAWD,IAAG,6BAA6B,SAAS;AAAA;AAAA,EACxD;AACJ;;;ACfA,SAAS,MAAAE,WAAU;AACnB,SAAS,OAAAC,YAAW;AAEb,IAAM,cAAcA,KAAID;AAAA,EAC3B;AAAA,EACA;AACJ,CAAC;AAEM,IAAM,kBAAkBC,KAAI,6HAA6H;;;ACPhK,SAAS,MAAAC,WAAU;AASP,SAIM,OAAAC,MAJN;AAFL,IAAM,QAAwB,CAAC,UAAsB;AACxD,QAAM,EAAE,MAAM,UAAU,WAAW,GAAG,UAAU,IAAI;AACpD,SAAQ;AAAA,IAAC;AAAA;AAAA,MACJ,GAAG;AAAA,MACJ,WAAWD,IAAG,YAAY,GAAG,SAAS;AAAA,MAErC;AAAA,gBAAS,gBAAAC;AAAA,UAAC;AAAA;AAAA,YACP,eAAY;AAAA,YACZ,WAAW,gBAAgB;AAAA,YAE1B;AAAA;AAAA,QACL;AAAA,QACC;AAAA;AAAA;AAAA,EACL;AACJ;;;AClBA,SAAS,KAAK,cAAc;AAC5B,SAAkB,gBAAgB;AAatB,SAKJ,OAAAC,MALI,QAAAC,aAAA;AANL,IAAM,gBAAwC,CAAC,UAA8B;AAChF,QAAM,EAAE,WAAW,WAAW,eAAe,cAAc,YAAY,GAAG,UAAU,IAAI;AACxF,QAAM;AAAA,IACF;AAAA,IACA;AAAA,EACJ,IAAI,SAAS,KAAK;AAClB,SAAQ,gBAAAA;AAAA,IAAC;AAAA;AAAA,MACL;AAAA,MACA,KAAK,UAAU;AAAA,MACf,WAAW;AAAA,MAEX;AAAA,wBAAAD;AAAA,UAAC;AAAA;AAAA,YACI,GAAG;AAAA,YACJ,MAAM,UAAU,SAAS;AAAA;AAAA,QAC7B;AAAA,QACA,gBAAAA;AAAA,UAAC;AAAA;AAAA,YACG,OAAM;AAAA,YAEN,0BAAAA;AAAA,cAAC;AAAA;AAAA,gBACG,QAAO;AAAA,gBACP,cAAY,UAAU,YAAY;AAAA,gBAClC,UAAU,UAAU;AAAA,gBACpB,SAAS,MAAM;AACX,6BAAW,aAAW;AAClB,2BAAO,CAAC;AAAA,kBACZ,CAAC;AAAA,gBACL;AAAA,gBAEC,oBAAW,gBAAAA;AAAA,kBAAC;AAAA;AAAA,oBACT,eAAY;AAAA;AAAA,gBAChB,IAAO,gBAAAA;AAAA,kBAAC;AAAA;AAAA,oBACJ,eAAY;AAAA;AAAA,gBAChB;AAAA;AAAA,YACJ;AAAA;AAAA,QACJ;AAAA;AAAA;AAAA,EACJ;AACJ;;;AC5CA,SAAS,QAAQ,SAAS;AAC1B,SAAkB,qBAAqB,cAAc;AAoB7C,gBAAAE,MAgBA,QAAAC,aAhBA;AAZD,IAAM,cAAoC,CAAC,UAA4B;AAC1E,QAAM;AAAA,IAAE;AAAA,IAAY,UAAU;AAAA,IAAO;AAAA,IAAc;AAAA,IAAe,cAAc;AAAA,IAAY;AAAA,IACxF;AAAA,IAAe,GAAG;AAAA,EAAU,IAAI;AACpC,QAAM,QAAQ,OAAyB,IAAI;AAC3C,sBAAoB,KAAK,MAAM;AAC3B,WAAO,MAAM;AAAA,EACjB,CAAC;AACD,SAAQ,gBAAAA;AAAA,IAAC;AAAA;AAAA,MACL;AAAA,MACA,KAAK,UAAU;AAAA,MACf,WAAW;AAAA,MAEX;AAAA,wBAAAD;AAAA,UAAC;AAAA;AAAA,YACI,GAAG;AAAA,YACJ,KAAK;AAAA,YACL,MAAK;AAAA,YACL,aAAW,WAAW,UAAU,WAAW;AAAA,YAC3C,eAAe,CAAC,SAAS;AACrB,8BAAgB,IAAI;AAAA,YACxB;AAAA;AAAA,QACJ;AAAA,QACA,gBAAAA;AAAA,UAAC;AAAA;AAAA,YACG,eAAY;AAAA,YAEZ,0BAAAA;AAAA,cAAC;AAAA;AAAA,gBACG,MAAM;AAAA;AAAA,YACV;AAAA;AAAA,QACJ;AAAA,QACA,gBAAAC;AAAA,UAAC;AAAA;AAAA,YACG,OAAM;AAAA,YAEL;AAAA,yBAAY,gBAAAD;AAAA,gBAAC;AAAA;AAAA,kBACV,MAAK;AAAA,kBAEJ;AAAA;AAAA,cACL;AAAA,cACA,gBAAAA;AAAA,gBAAC;AAAA;AAAA,kBACG,QAAO;AAAA,kBACP,cAAY;AAAA,kBACZ,UAAU,UAAU,YAAY,UAAU;AAAA,kBAC1C,SAAS,MAAM;AACX,0BAAM,UAAU,MAAM;AACtB,wBAAI,CAAC,WAAW,QAAQ,QAAQ,WAAW,KAAK,QAAQ,UAAU;AAC9D;AAAA,oBACJ;AACA,wBAAI,QAAQ,UAAU,IAAI;AACtB,0BAAI,UAAU,UAAU,QAAW;AAC/B,gCAAQ,QAAQ;AAAA,sBACpB;AACA,sCAAgB,EAAE;AAAA,oBACtB;AACA,4BAAQ,MAAM;AAAA,kBAClB;AAAA,kBAEA,0BAAAA;AAAA,oBAAC;AAAA;AAAA,sBACG,eAAY;AAAA;AAAA,kBAChB;AAAA;AAAA,cACJ;AAAA;AAAA;AAAA,QACJ;AAAA;AAAA;AAAA,EACJ;AACJ;;;ACtEA,SAAS,qBAAqB;AAC9B,SAAS,MAAAE,WAAU;AACnB,OAAwE;AA2B5D,gBAAAC,YAAA;AApBL,IAAM,WAA8B,CAAC,UAAyB;AACjE,QAAM,YAAY,kBAAkB;AACpC,QAAM;AAAA,IAAE,cAAc,aAAa;AAAA,IAAY;AAAA,IAAW;AAAA,IAAU;AAAA,IAAe;AAAA,IAAK,WAAW;AAAA,IAC/F;AAAA,IAAW;AAAA,IAAS,OAAO;AAAA,IAAG;AAAA,IAAO,GAAG;AAAA,EAAU,IAAI;AAC1D,MAAI,cAAc,WAAc,CAAC,OAAO,SAAS,SAAS,KAAK,aAAa,IAAI;AAC5E,UAAM,IAAI,WAAW,iDAAiD;AAAA,EAC1E;AACA,MAAI,YAAY,WAAc,CAAC,OAAO,cAAc,OAAO,KAAK,UAAU,OAAO;AAC7E,UAAM,IAAI,WAAW,gEAAgE;AAAA,EACzF;AACA,MAAI,cAAc,UAAa,YAAY,QAAW;AAClD,UAAM,IAAI,WAAW,yDAAyD;AAAA,EAClF;AACA,QAAM,YAAY,cAAmC,EAAE,IAAI,CAAC;AAC5D,QAAM,SAA6E;AAAA,IAC/E,2BAA2B;AAAA,IAC3B,+BAA+B;AAAA,IAC/B,GAAG;AAAA,IACH,GAAI,YAAY,cAAc,SAAY,EAAE,UAAU,IAAI,CAAC;AAAA,EAC/D;AACA,SAAQ,gBAAAA;AAAA,IAAC;AAAA;AAAA,MACJ,GAAG;AAAA,MACJ,KAAK;AAAA,MACL;AAAA,MACA,OAAO;AAAA,MACP,iBAAe,YAAY;AAAA,MAC3B,iBAAe,YAAY,UAAa;AAAA,MACxC,UAAU,CAAC,UAAU;AACjB,mBAAW,KAAK;AAChB,wBAAgB,MAAM,cAAc,KAAK;AAAA,MAC7C;AAAA,MACA,WAAWD,IAAG,gBAAgB,EAAE,MAAM,YAAY,CAAC,GAAG,iDAAiD,aAAa,2DAA2D,WAAW,sFAAsF,4BAA4B,SAAS;AAAA;AAAA,EACzT;AACJ;",
6
- "names": ["cn", "cn", "jsx", "cn", "jsx", "cn", "jsx", "cn", "cva", "cn", "jsx", "jsx", "jsxs", "jsx", "jsxs", "cn", "jsx"]
3
+ "sources": ["../src/shared/input-group-context.ts", "../src/shared/style.ts", "../src/input/input.tsx", "../src/input-group/input-group.tsx", "../src/input-group-addon/input-group-addon.tsx", "../src/input-group-button/input-group-button.tsx", "../src/label/style.ts", "../src/label/label.tsx", "../src/password-input/password-input.tsx", "../src/search-input/search-input.tsx", "../src/textarea/resize-handle.tsx", "../src/textarea/use-textarea-footer-height.ts", "../src/textarea/use-textarea-resize.ts", "../src/textarea/textarea.tsx"],
4
+ "sourcesContent": ["/**\n * @author WMXPY\n * @package Input\n * @namespace Shared\n * @description Input Group Context\n */\n\nimport { createContext, useContext } from \"react\";\n\nexport const inputGroupContext = createContext<\"standard\" | \"small\" | null>(null);\nexport const useInputGroupSize = () => {\n return useContext(inputGroupContext);\n};\n", "/**\n * @author WMXPY\n * @package Input\n * @namespace Shared\n * @description Style\n */\n\nimport { cn } from \"@gridui/style-utils\";\nimport { cva } from \"class-variance-authority\";\n\nexport const controlStyles = cva(cn(\n \"gridui-control\",\n \"gridui-u-input--min-w-0 gridui-u-input--w-full gridui-u-input--box-border gridui-u-input--border gridui-u-input--border-solid gridui-u-input--border-border gridui-u-input--rounded-control gridui-u-input--text-foreground gridui-u-input--bg-background gridui-u-input--type-input\",\n \"gridui-u-input--disabled:opacity-50 gridui-u-input--disabled:cursor-not-allowed\",\n \"gridui-u-input--aria-invalid:border-danger-foreground\",\n));\n\nexport const controlStandardStyles = cva(cn(\n \"gridui-control-standard\",\n \"gridui-u-input--p-gridui-8 gridui-u-input--min-h-gridui-control\",\n));\n\nexport const controlSmallStyles = cva(cn(\n \"gridui-control-small\",\n \"gridui-u-input--py-gridui-4 gridui-u-input--px-gridui-8 gridui-u-input--min-h-gridui-control-small\",\n));\n\nexport const controlVariants = cva(controlStyles(), {\n variants: {\n size: {\n standard: controlStandardStyles(),\n small: controlSmallStyles(),\n },\n },\n defaultVariants: { size: \"standard\" },\n});\n", "/**\n * @author WMXPY\n * @package Input\n * @namespace Input\n * @description Input\n */\n\nimport { useInputGroupSize } from \"../shared/input-group-context\";\nimport { controlVariants } from \"../shared/style\";\nimport type { TextControlOptions } from \"../shared/text-control-options\";\nimport { cn } from \"@gridui/style-utils\";\nimport type { ComponentPropsWithRef, FC } from \"react\";\n\nexport type InputProps = ComponentPropsWithRef<\"input\"> & TextControlOptions;\n\nexport const Input: FC<InputProps> = (props: InputProps) => {\n const groupSize = useInputGroupSize();\n const {\n size,\n controlSize = groupSize ?? \"standard\",\n className,\n onChange,\n onValueChange,\n ...restProps\n } = props;\n return (<input\n {...restProps}\n size={size}\n onChange={(event) => {\n onChange?.(event);\n onValueChange?.(event.currentTarget.value);\n }}\n className={cn(controlVariants({ size: controlSize }), groupSize && \"gridui-u-input--flex-1 gridui-u-input--w-0 gridui-u-input--border-0 gridui-u-input--bg-transparent\", className)}\n />);\n};\n", "/**\n * @author WMXPY\n * @package Input\n * @namespace InputGroup\n * @description Input Group\n */\n\nimport { inputGroupContext } from \"../shared/input-group-context\";\nimport { cn } from \"@gridui/style-utils\";\nimport type { ComponentPropsWithRef, FC } from \"react\";\n\nexport type InputGroupProps = ComponentPropsWithRef<\"div\"> & {\n readonly controlSize?: \"standard\" | \"small\";\n};\nexport const InputGroup: FC<InputGroupProps> = (props: InputGroupProps) => {\n const { controlSize = \"standard\", className, children, ...restProps } = props;\n return (<inputGroupContext.Provider\n value={controlSize}\n >\n <div\n {...restProps}\n data-control-size={controlSize}\n data-gridui-outline-target=\"\"\n className={cn(\"gridui-input-group\", className)}\n >\n {children}\n </div>\n </inputGroupContext.Provider>);\n};\n", "/**\n * @author WMXPY\n * @package Input\n * @namespace InputGroupAddon\n * @description Input Group Addon\n */\n\nimport { cn } from \"@gridui/style-utils\";\nimport type { ComponentPropsWithRef, FC } from \"react\";\n\nexport type InputGroupAddonProps = ComponentPropsWithRef<\"div\"> & {\n readonly align?: \"inline-start\" | \"inline-end\" | \"block-start\" | \"block-end\";\n};\nexport const InputGroupAddon: FC<InputGroupAddonProps> = (props: InputGroupAddonProps) => {\n const { align = \"inline-start\", className, ...restProps } = props;\n return (<div\n {...restProps}\n data-align={align}\n className={cn(\"gridui-input-group-addon\", className)}\n />);\n};\n", "/**\n * @author WMXPY\n * @package Input\n * @namespace InputGroupButton\n * @description Input Group Button\n */\n\nimport { useInputGroupSize } from \"../shared/input-group-context\";\nimport { Button, type ButtonProps } from \"@gridui/button\";\nimport { cn } from \"@gridui/style-utils\";\nimport type { FC } from \"react\";\n\nexport type InputGroupButtonProps = ButtonProps;\nexport const InputGroupButton: FC<InputGroupButtonProps> = (props: InputGroupButtonProps) => {\n const size = useInputGroupSize() ?? \"standard\";\n const { className, ...restProps } = props;\n return (<Button\n variant=\"quiet\"\n size={size}\n {...restProps}\n className={cn(\"gridui-input-group-button\", className)}\n />);\n};\n", "/**\n * @author WMXPY\n * @package Input\n * @namespace Label\n * @description Style\n */\n\nimport { cn } from \"@gridui/style-utils\";\nimport { cva } from \"class-variance-authority\";\n\nexport const labelStyles = cva(cn(\n \"gridui-label\",\n \"gridui-u-input--inline-flex gridui-u-input--items-center gridui-u-input--gap-gridui-4 gridui-u-input--type-ui\",\n));\n\nexport const labelIconStyles = cva(\"gridui-u-input--inline-flex gridui-u-input--shrink-0 gridui-u-input--text-foreground gridui-u-input--[&>svg]:size-gridui-16\");\n", "/**\n * @author WMXPY\n * @package Input\n * @namespace Label\n * @description Label\n */\n\nimport { labelIconStyles, labelStyles } from \"./style\";\nimport { cn } from \"@gridui/style-utils\";\nimport type { ComponentPropsWithRef, FC, ReactNode } from \"react\";\n\nexport type LabelProps = ComponentPropsWithRef<\"label\"> & {\n readonly icon?: ReactNode;\n};\n\nexport const Label: FC<LabelProps> = (props: LabelProps) => {\n const { icon, children, className, ...restProps } = props;\n return (<label\n {...restProps}\n className={cn(labelStyles(), className)}\n >\n {icon && (<span\n aria-hidden=\"true\"\n className={labelIconStyles()}\n >\n {icon}\n </span>)}\n {children}\n </label>);\n};\n", "/**\n * @author WMXPY\n * @package Input\n * @namespace PasswordInput\n * @description Password Input\n */\n\nimport { Input, type InputProps } from \"../input/input\";\nimport { InputGroup } from \"../input-group/input-group\";\nimport { InputGroupAddon } from \"../input-group-addon/input-group-addon\";\nimport { InputGroupButton } from \"../input-group-button/input-group-button\";\nimport { Eye, EyeOff } from \"lucide-react\";\nimport { type FC, useState } from \"react\";\n\nexport type PasswordInputProps = Omit<InputProps, \"type\"> & {\n readonly showLabel: string;\n readonly hideLabel: string;\n readonly rootClassName?: string;\n};\nexport const PasswordInput: FC<PasswordInputProps> = (props: PasswordInputProps) => {\n const { showLabel, hideLabel, rootClassName, controlSize = \"standard\", ...restProps } = props;\n const [\n visible,\n setVisible,\n ] = useState(false);\n return (<InputGroup\n controlSize={controlSize}\n dir={restProps.dir}\n className={rootClassName}\n >\n <Input\n {...restProps}\n type={visible ? \"text\" : \"password\"}\n />\n <InputGroupAddon\n align=\"inline-end\"\n >\n <InputGroupButton\n layout=\"icon\"\n aria-label={visible ? hideLabel : showLabel}\n disabled={restProps.disabled}\n onClick={() => {\n setVisible(current => {\n return !current;\n });\n }}\n >\n {visible ? (<EyeOff\n aria-hidden=\"true\"\n />) : (<Eye\n aria-hidden=\"true\"\n />)}\n </InputGroupButton>\n </InputGroupAddon>\n </InputGroup>);\n};\n", "/**\n * @author WMXPY\n * @package Input\n * @namespace SearchInput\n * @description Search Input\n */\n\nimport { Input, type InputProps } from \"../input/input\";\nimport { InputGroup } from \"../input-group/input-group\";\nimport { InputGroupAddon } from \"../input-group-addon/input-group-addon\";\nimport { InputGroupButton } from \"../input-group-button/input-group-button\";\nimport { Search, X } from \"lucide-react\";\nimport { type FC, useImperativeHandle, useRef } from \"react\";\n\nexport type SearchInputProps = Omit<InputProps, \"type\"> & {\n readonly clearLabel: string;\n readonly loading?: boolean;\n readonly loadingLabel?: string;\n readonly rootClassName?: string;\n};\nexport const SearchInput: FC<SearchInputProps> = (props: SearchInputProps) => {\n const { clearLabel, loading = false, loadingLabel, rootClassName, controlSize = \"standard\", ref,\n onValueChange, ...restProps } = props;\n const input = useRef<HTMLInputElement>(null);\n useImperativeHandle(ref, () => {\n return input.current!;\n });\n return (<InputGroup\n controlSize={controlSize}\n dir={restProps.dir}\n className={rootClassName}\n >\n <Input\n {...restProps}\n ref={input}\n type=\"search\"\n aria-busy={loading || restProps[\"aria-busy\"]}\n onValueChange={(next) => {\n onValueChange?.(next);\n }}\n />\n <InputGroupAddon\n aria-hidden=\"true\"\n >\n <Search\n size={16}\n />\n </InputGroupAddon>\n <InputGroupAddon\n align=\"inline-end\"\n >\n {loading && (<span\n role=\"status\"\n >\n {loadingLabel}\n </span>)}\n <InputGroupButton\n layout=\"icon\"\n aria-label={clearLabel}\n disabled={restProps.disabled || restProps.readOnly}\n onClick={() => {\n const element = input.current;\n if (!element || element.matches(\":disabled\") || element.readOnly) {\n return;\n }\n if (element.value !== \"\") {\n if (restProps.value === undefined) {\n element.value = \"\";\n }\n onValueChange?.(\"\");\n }\n element.focus();\n }}\n >\n <X\n aria-hidden=\"true\"\n />\n </InputGroupButton>\n </InputGroupAddon>\n </InputGroup>);\n};\n", "/**\n * @author WMXPY\n * @package Input\n * @namespace Textarea\n * @description Resize Handle\n */\n\nimport { GripHorizontal, MoveVertical } from \"lucide-react\";\nimport type { ComponentPropsWithRef, FC } from \"react\";\n\nexport type ResizeHandleProps = ComponentPropsWithRef<\"button\"> & {\n readonly variant: \"corner\" | \"edge\" | \"footer\";\n readonly label: string;\n readonly controlId: string;\n readonly dragging: boolean;\n};\n\nexport const ResizeHandle: FC<ResizeHandleProps> = (props: ResizeHandleProps) => {\n const { variant, label, controlId, dragging, ...restProps } = props;\n const Icon = variant === \"edge\" ? GripHorizontal : MoveVertical;\n return (<button\n {...restProps}\n type=\"button\"\n className=\"gridui-textarea-resize-handle\"\n data-variant={variant}\n data-dragging={dragging || undefined}\n aria-label={label}\n aria-controls={controlId}\n aria-keyshortcuts=\"ArrowUp ArrowDown Home End Enter Space\"\n >\n <Icon\n size={16}\n aria-hidden=\"true\"\n />\n </button>);\n};\n", "/**\n * @author WMXPY\n * @package Input\n * @namespace Textarea\n * @description Use Textarea Footer Height\n */\n\nimport { useLayoutEffect, useRef, useState } from \"react\";\n\nexport const useTextareaFooterHeight = (visible: boolean) => {\n const ref = useRef<HTMLDivElement>(null);\n const [\n height,\n setHeight,\n ] = useState<number>();\n useLayoutEffect(() => {\n const footer = ref.current;\n if (!visible || !footer) {\n return;\n }\n const measure = () => {\n setHeight(parseFloat(footer.ownerDocument.defaultView!.getComputedStyle(footer).height));\n };\n measure();\n const observer = new ResizeObserver(measure);\n observer.observe(footer);\n return () => {\n observer.disconnect();\n };\n }, [visible]);\n return {\n ref,\n height: visible ? height : undefined,\n };\n};\n", "/**\n * @author WMXPY\n * @package Input\n * @namespace Textarea\n * @description Use Textarea Resize\n */\n\nimport { isComposingKeyEvent, listenForDragCancellation } from \"@gridui/interaction\";\nimport { type ComponentPropsWithRef, type CSSProperties, type RefObject, useCallback, useEffect, useLayoutEffect, useRef, useState } from \"react\";\n\ntype ResizeOptions = {\n readonly frame: RefObject<HTMLDivElement | null>;\n readonly input: RefObject<HTMLTextAreaElement | null>;\n readonly enabled: boolean;\n readonly disabled: boolean;\n};\n\ntype Drag = {\n readonly pointerId: number;\n readonly startY: number;\n readonly startHeight: number;\n readonly previousHeight: CSSProperties[\"height\"];\n readonly scale: number;\n readonly handle: HTMLButtonElement;\n readonly cleanup: () => void;\n};\n\nconst getResizeGeometry = (frame: HTMLDivElement) => {\n const style = frame.ownerDocument.defaultView!.getComputedStyle(frame);\n return {\n height: parseFloat(style.height),\n maximum: style.maxHeight === \"none\" ? undefined : style.maxHeight,\n scale: frame.getBoundingClientRect().height / frame.offsetHeight || 1,\n };\n};\n\nconst releaseDrag = (drag: Drag): void => {\n drag.cleanup();\n if (drag.handle.hasPointerCapture(drag.pointerId)) {\n drag.handle.releasePointerCapture(drag.pointerId);\n }\n};\n\nexport const useTextareaResize = ({ frame, input, enabled, disabled }: ResizeOptions): {\n height: CSSProperties[\"height\"];\n dragging: boolean;\n handleProps: ComponentPropsWithRef<\"button\">;\n} => {\n const [\n height,\n setHeight,\n ] = useState<CSSProperties[\"height\"]>();\n const [\n dragging,\n setDragging,\n ] = useState(false);\n const drag = useRef<Drag | null>(null);\n const blocked = (): boolean => {\n return !enabled || disabled || !input.current || input.current.matches(\":disabled\") || input.current.inert;\n };\n const finish = (restore: boolean): void => {\n const active = drag.current;\n if (!active) {\n return;\n }\n drag.current = null;\n releaseDrag(active);\n if (restore) {\n setHeight(active.previousHeight);\n }\n setDragging(false);\n };\n const cancel = useRef(() => {\n return;\n });\n useLayoutEffect(() => {\n cancel.current = () => {\n finish(true);\n };\n if (blocked()) {\n finish(true);\n }\n });\n useLayoutEffect(() => {\n if (!enabled) {\n setHeight(undefined);\n }\n }, [enabled]);\n useEffect(() => {\n return () => {\n cancel.current();\n };\n }, []);\n const handleRef = useCallback((handle: HTMLButtonElement | null) => {\n if (!handle) {\n return;\n }\n return () => {\n if (handle.ownerDocument.activeElement === handle) {\n input.current?.focus({ preventScroll: true });\n }\n };\n }, [input]);\n const update = (next: CSSProperties[\"height\"]): void => {\n if (blocked() || !frame.current) {\n finish(true);\n return;\n }\n // CSS resolves constraints, including percentages and content-dependent minima.\n setHeight(typeof next === \"number\" ? Math.max(0, next) : next);\n };\n const reset = (): void => {\n if (!blocked()) {\n finish(false);\n setHeight(undefined);\n }\n };\n const handleProps: ComponentPropsWithRef<\"button\"> = {\n ref: handleRef,\n onPointerDown: (event) => {\n if (blocked() || event.button !== 0 || !event.isPrimary || drag.current || !frame.current) {\n return;\n }\n const bounds = getResizeGeometry(frame.current);\n const handle = event.currentTarget;\n event.preventDefault();\n handle.focus({ preventScroll: true });\n drag.current = {\n pointerId: event.pointerId,\n startY: event.clientY,\n startHeight: bounds.height,\n previousHeight: height,\n scale: bounds.scale,\n handle,\n cleanup: listenForDragCancellation(handle, () => {\n cancel.current();\n }),\n };\n handle.setPointerCapture(event.pointerId);\n setDragging(true);\n },\n onPointerMove: (event) => {\n const active = drag.current;\n if (active?.pointerId === event.pointerId) {\n update(active.startHeight + (event.clientY - active.startY) / active.scale);\n }\n },\n onPointerUp: (event) => {\n if (drag.current?.pointerId === event.pointerId) {\n finish(blocked());\n }\n },\n onPointerCancel: (event) => {\n if (drag.current?.pointerId === event.pointerId) {\n finish(true);\n }\n },\n onLostPointerCapture: (event) => {\n if (drag.current?.pointerId === event.pointerId) {\n finish(true);\n }\n },\n onKeyDown: (event) => {\n if (blocked() || drag.current || !frame.current || isComposingKeyEvent(event.nativeEvent)) {\n return;\n }\n const bounds = getResizeGeometry(frame.current);\n const step = event.shiftKey ? 64 : 16;\n let next: CSSProperties[\"height\"];\n switch (event.key) {\n case \"ArrowUp\": next = bounds.height - step; break;\n case \"ArrowDown\": next = bounds.height + step; break;\n case \"Home\": next = 0; break;\n case \"End\":\n if (bounds.maximum === undefined) {\n return;\n }\n next = bounds.maximum;\n break;\n default: return;\n }\n event.preventDefault();\n update(next);\n },\n onClick: (event) => {\n // Keyboard and assistive-technology activation restore the natural height.\n if (event.detail === 0) {\n reset();\n }\n },\n onDoubleClick: reset,\n };\n return {\n height,\n dragging,\n handleProps,\n };\n};\n", "/**\n * @author WMXPY\n * @package Input\n * @namespace Textarea\n * @description Textarea\n */\n\nimport { useInputGroupSize } from \"../shared/input-group-context\";\nimport { controlStyles } from \"../shared/style\";\nimport type { TextControlOptions } from \"../shared/text-control-options\";\nimport { ResizeHandle } from \"./resize-handle\";\nimport { useTextareaFooterHeight } from \"./use-textarea-footer-height\";\nimport { useTextareaResize } from \"./use-textarea-resize\";\nimport { useScrollbars } from \"@gridui/overlay\";\nimport { cn } from \"@gridui/style-utils\";\nimport { type ComponentPropsWithRef, type CSSProperties, type FC, type ReactNode, useId, useImperativeHandle, useRef } from \"react\";\n\nexport type TextareaProps = ComponentPropsWithRef<\"textarea\"> & TextControlOptions & {\n readonly autoSize?: boolean;\n readonly maxHeight?: number;\n readonly maxRows?: number;\n /** Manual resize affordance. Automatic sizing always hides it. */\n readonly resizeHandle?: \"corner\" | \"edge\" | \"footer\" | false;\n readonly resizeLabel?: string;\n /** Caller-owned footer content; footer handles share this row. */\n readonly footer?: ReactNode;\n};\nexport const Textarea: FC<TextareaProps> = (props: TextareaProps) => {\n const groupSize = useInputGroupSize();\n const { controlSize = groupSize ?? \"standard\", className, onChange, onValueChange, ref, autoSize = false,\n maxHeight, maxRows, rows = 2, style, resizeHandle = \"edge\", resizeLabel = \"Resize text area\", footer, ...restProps } = props;\n if (maxHeight !== undefined && (!Number.isFinite(maxHeight) || maxHeight <= 0)) {\n throw new RangeError(\"Textarea maxHeight must be finite and positive.\");\n }\n if (maxRows !== undefined && (!Number.isSafeInteger(maxRows) || maxRows < rows)) {\n throw new RangeError(\"Textarea maxRows must be an integer at least as large as rows.\");\n }\n if (maxHeight !== undefined && maxRows !== undefined) {\n throw new RangeError(\"Textarea accepts either maxRows or maxHeight, not both.\");\n }\n const frame = useRef<HTMLDivElement>(null);\n const input = useRef<HTMLTextAreaElement>(null);\n const generatedId = useId();\n const inputId = restProps.id ?? generatedId;\n useImperativeHandle(ref, () => {\n return input.current!;\n }, []);\n const scrollRef = useScrollbars<HTMLTextAreaElement>({ ref: input });\n const manual = !autoSize && resizeHandle !== false;\n const resize = useTextareaResize({\n frame,\n input,\n enabled: manual,\n disabled: Boolean(restProps.disabled),\n });\n const hasFooter = footer !== undefined && footer !== null && typeof footer !== \"boolean\";\n const showFooter = hasFooter || manual && resizeHandle === \"footer\";\n const footerLayout = useTextareaFooterHeight(showFooter);\n const handle = manual ? (<ResizeHandle\n {...resize.handleProps}\n variant={resizeHandle}\n label={resizeLabel}\n controlId={inputId}\n dragging={resize.dragging}\n disabled={restProps.disabled}\n />) : null;\n const sizing: CSSProperties & Record<`--${string}`, string | number | undefined> = {\n \"--_gridui-textarea-rows\": rows,\n \"--_gridui-textarea-max-rows\": maxRows,\n \"--_gridui-textarea-footer-height\": footerLayout.height === undefined ? undefined : `${footerLayout.height}px`,\n ...style,\n ...(maxHeight !== undefined ? { maxHeight } : {}),\n ...(manual && resize.height !== undefined ? { height: resize.height } : {}),\n };\n return (<div\n ref={frame}\n dir={restProps.dir}\n lang={restProps.lang}\n hidden={restProps.hidden}\n inert={restProps.inert}\n style={sizing}\n className={cn(controlStyles(), \"gridui-textarea-frame\", restProps[\"aria-invalid\"] && restProps[\"aria-invalid\"] !== \"false\" && \"gridui-u-input--border-danger-foreground\", groupSize && \"gridui-u-input--border-0 gridui-u-input--border-inherit gridui-u-input--bg-transparent\", className)}\n data-gridui-outline-target={groupSize === null ? \"\" : undefined}\n data-control-size={controlSize}\n data-grouped={groupSize !== null || undefined}\n data-autosize={autoSize || undefined}\n data-max-rows={maxRows !== undefined || undefined}\n data-resize-handle={manual ? resizeHandle : undefined}\n data-footer={hasFooter || undefined}\n >\n <textarea\n {...restProps}\n id={inputId}\n ref={scrollRef}\n rows={rows}\n onChange={(event) => {\n onChange?.(event);\n onValueChange?.(event.currentTarget.value);\n }}\n className=\"gridui-textarea\"\n />\n {manual && resizeHandle !== \"footer\" && (<div\n className=\"gridui-textarea-resize-row\"\n >\n {handle}\n </div>)}\n {showFooter && (<div\n ref={footerLayout.ref}\n className=\"gridui-textarea-footer\"\n >\n <div\n className=\"gridui-textarea-footer-content\"\n >\n {footer}\n </div>\n {resizeHandle === \"footer\" && handle}\n </div>)}\n </div>);\n};\n"],
5
+ "mappings": "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",
6
+ "names": ["cn", "cn", "jsx", "cn", "jsx", "cn", "jsx", "cn", "cva", "cn", "jsx", "jsx", "jsxs", "jsx", "jsxs", "jsx", "useRef", "useState", "useLayoutEffect", "useRef", "useState", "cn", "useImperativeHandle", "useRef", "jsx", "jsxs"]
7
7
  }
package/dist/styles.css CHANGED
@@ -8,9 +8,6 @@
8
8
  .gridui-u-input--inline-flex {
9
9
  display: inline-flex;
10
10
  }
11
- .gridui-u-input--min-h-\(--_gridui-textarea-min-height\) {
12
- min-height: var(--_gridui-textarea-min-height);
13
- }
14
11
  .gridui-u-input--min-h-gridui-control {
15
12
  min-height: var(--gridui-control-min-height);
16
13
  }
@@ -32,21 +29,12 @@
32
29
  .gridui-u-input--shrink-0 {
33
30
  flex-shrink: 0;
34
31
  }
35
- .gridui-u-input--resize-none {
36
- resize: none;
37
- }
38
- .gridui-u-input--resize-y {
39
- resize: vertical;
40
- }
41
32
  .gridui-u-input--items-center {
42
33
  align-items: center;
43
34
  }
44
35
  .gridui-u-input--gap-gridui-4 {
45
36
  gap: var(--gridui-space-4);
46
37
  }
47
- .gridui-u-input--overflow-auto {
48
- overflow: auto;
49
- }
50
38
  .gridui-u-input--rounded-control {
51
39
  border-radius: var(--gridui-control-radius);
52
40
  }
@@ -65,6 +53,12 @@
65
53
  .gridui-u-input--border-border {
66
54
  border-color: var(--gridui-border);
67
55
  }
56
+ .gridui-u-input--border-danger-foreground {
57
+ border-color: var(--gridui-danger-foreground);
58
+ }
59
+ .gridui-u-input--border-inherit {
60
+ border-color: inherit;
61
+ }
68
62
  .gridui-u-input--bg-background {
69
63
  background-color: var(--gridui-background);
70
64
  }
@@ -130,20 +124,20 @@
130
124
  background: transparent;
131
125
  outline: none;
132
126
  }
133
- > textarea.gridui-control {
127
+ > .gridui-textarea-frame {
134
128
  flex-basis: 100%;
135
129
  width: 100%;
136
130
  }
137
- &:has(> .gridui-control:focus-visible) {
131
+ &:has(> .gridui-control:focus-visible, > .gridui-textarea-frame > textarea:focus-visible) {
138
132
  z-index: var(--gridui-z-interaction);
139
133
  outline: var(--gridui-focus-width) solid var(--gridui-focus-color);
140
134
  outline-offset: var(--gridui-focus-offset);
141
135
  }
142
- &:has(> .gridui-control[aria-invalid="true"]) {
136
+ &:has(> .gridui-control[aria-invalid="true"], > .gridui-textarea-frame > textarea[aria-invalid="true"]) {
143
137
  border-color: var(--gridui-danger-foreground);
144
138
  }
145
139
  @media (hover: hover) and (pointer: fine) {
146
- &:has(> .gridui-control:hover:not(:disabled, [readonly])):not(:has(:focus-visible)) {
140
+ &:has(> .gridui-control:not(.gridui-textarea-frame):hover:not(:disabled, [readonly]), > .gridui-textarea-frame > textarea:hover:not(:disabled, [readonly])):not(:has(:focus-visible)) {
147
141
  outline: var(--gridui-hover-width) solid var(--gridui-focus-color);
148
142
  @supports (color: color-mix(in lab, red, red)) {
149
143
  outline: var(--gridui-hover-width) solid color-mix(in srgb, var(--gridui-focus-color) var(--gridui-hover-opacity), transparent);
@@ -177,18 +171,154 @@
177
171
  .gridui-input-group-button {
178
172
  flex: none;
179
173
  }
180
- .gridui-textarea[data-autosize] {
181
- --_gridui-textarea-inset: calc(2 * var(--gridui-space-8) + 2px);
182
- --_gridui-textarea-minimum: var(--gridui-control-min-height);
174
+ .gridui-textarea-frame {
175
+ --_gridui-textarea-padding: var(--gridui-space-8);
176
+ --_gridui-textarea-control-minimum: var(--gridui-control-min-height);
177
+ --_gridui-textarea-resize-height: 0px;
178
+ --_gridui-textarea-footer-height: 0px;
179
+ --_gridui-textarea-border: 2px;
180
+ --_gridui-textarea-min-body: max(calc(var(--_gridui-textarea-control-minimum) - 2px), calc(var(--_gridui-textarea-rows) * 1lh + 2 * var(--_gridui-textarea-padding)));
181
+ --_gridui-textarea-chrome: calc(var(--_gridui-textarea-resize-height) + var(--_gridui-textarea-footer-height) + var(--_gridui-textarea-border));
182
+ position: relative;
183
+ display: flex;
184
+ flex-direction: column;
185
+ min-height: calc(var(--_gridui-textarea-min-body) + var(--_gridui-textarea-chrome));
186
+ padding: 0;
187
+ &[data-control-size="small"] {
188
+ --_gridui-textarea-padding: var(--gridui-space-4);
189
+ --_gridui-textarea-control-minimum: var(--gridui-control-min-height-small);
190
+ }
191
+ &[data-grouped] {
192
+ --_gridui-textarea-border: 0px;
193
+ border: 0;
194
+ background: transparent;
195
+ }
196
+ &[data-resize-handle="edge"] {
197
+ --_gridui-textarea-resize-height: var(--gridui-space-24);
198
+ }
199
+ &[data-resize-handle="corner"] {
200
+ --_gridui-textarea-resize-height: var(--gridui-space-32);
201
+ }
202
+ &:is([data-resize-handle="footer"], [data-footer]) {
203
+ --_gridui-textarea-footer-height: var(--gridui-control-min-height);
204
+ }
205
+ &[data-max-rows] {
206
+ max-height: calc(var(--_gridui-textarea-max-rows) * 1lh + 2 * var(--_gridui-textarea-padding) + var(--_gridui-textarea-chrome));
207
+ }
208
+ &:has(> textarea[aria-invalid="true"]) {
209
+ border-color: var(--gridui-danger-foreground);
210
+ }
211
+ &:not([data-grouped]):has(> textarea:focus-visible) {
212
+ outline: var(--gridui-focus-width) solid var(--gridui-focus-color);
213
+ outline-offset: var(--gridui-focus-offset);
214
+ }
215
+ @media (hover: hover) and (pointer: fine) {
216
+ &:not([data-grouped]):has(> textarea:hover:not(:disabled, [readonly])):not(:has(:focus-visible)) {
217
+ outline: var(--gridui-hover-width) solid var(--gridui-focus-color);
218
+ @supports (color: color-mix(in lab, red, red)) {
219
+ outline: var(--gridui-hover-width) solid color-mix(in srgb, var(--gridui-focus-color) var(--gridui-hover-opacity), transparent);
220
+ }
221
+ outline-offset: var(--gridui-hover-offset);
222
+ }
223
+ }
224
+ }
225
+ .gridui-textarea {
226
+ display: block;
227
+ flex: 1 1 auto;
228
+ min-width: 0;
229
+ min-height: 0;
230
+ width: 100%;
231
+ margin: 0;
232
+ padding: var(--_gridui-textarea-padding) var(--gridui-space-8);
233
+ border: 0;
234
+ border-radius: 0;
235
+ color: inherit;
236
+ background: transparent;
237
+ font: inherit;
238
+ resize: none;
239
+ outline: none;
240
+ overflow: auto;
241
+ box-sizing: border-box;
242
+ &:disabled {
243
+ opacity: 0.5;
244
+ cursor: not-allowed;
245
+ }
246
+ }
247
+ .gridui-textarea-frame[data-autosize] > .gridui-textarea {
183
248
  field-sizing: content;
184
249
  height: auto;
185
- --_gridui-textarea-min-height: max(var(--_gridui-textarea-minimum), calc(var(--_gridui-textarea-rows) * 1lh + var(--_gridui-textarea-inset)));
186
- &.gridui-control-small {
187
- --_gridui-textarea-inset: calc(2 * var(--gridui-space-4) + 2px);
188
- --_gridui-textarea-minimum: var(--gridui-control-min-height-small);
250
+ }
251
+ .gridui-textarea-resize-row {
252
+ display: flex;
253
+ flex: none;
254
+ justify-content: flex-end;
255
+ height: var(--_gridui-textarea-resize-height);
256
+ border-color: inherit;
257
+ }
258
+ .gridui-textarea-footer {
259
+ display: flex;
260
+ flex: none;
261
+ align-items: stretch;
262
+ min-height: var(--gridui-control-min-height);
263
+ border-top: 1px solid;
264
+ border-color: inherit;
265
+ background: var(--gridui-surface);
266
+ box-sizing: border-box;
267
+ }
268
+ .gridui-textarea-footer-content {
269
+ display: flex;
270
+ flex: 1;
271
+ flex-wrap: wrap;
272
+ align-items: center;
273
+ gap: var(--gridui-space-8);
274
+ min-width: 0;
275
+ padding: var(--gridui-space-8);
276
+ }
277
+ .gridui-textarea-resize-handle {
278
+ display: flex;
279
+ flex: none;
280
+ align-items: center;
281
+ justify-content: center;
282
+ width: var(--gridui-control-min-height);
283
+ margin: 0;
284
+ padding: 0;
285
+ border: 0;
286
+ border-radius: 0;
287
+ border-color: inherit;
288
+ color: var(--gridui-muted);
289
+ background: transparent;
290
+ cursor: ns-resize;
291
+ touch-action: none;
292
+ user-select: none;
293
+ &[data-variant="edge"] {
294
+ width: 100%;
295
+ border-top: 1px solid;
296
+ border-color: inherit;
297
+ background: var(--gridui-surface);
189
298
  }
190
- &[data-max-rows] {
191
- max-height: calc(var(--_gridui-textarea-max-rows) * 1lh + var(--_gridui-textarea-inset));
299
+ &[data-variant="corner"] {
300
+ border-top: 1px solid;
301
+ border-inline-start: 1px solid;
302
+ border-color: inherit;
303
+ }
304
+ &[data-variant="footer"] {
305
+ border-inline-start: 1px solid;
306
+ border-color: inherit;
307
+ }
308
+ &:disabled {
309
+ cursor: not-allowed;
310
+ > svg {
311
+ opacity: 0.5;
312
+ }
313
+ }
314
+ &:is(:hover, [data-dragging]):not(:disabled) {
315
+ color: var(--gridui-foreground);
316
+ background: var(--gridui-surface);
317
+ }
318
+ }
319
+ @media (pointer: coarse) {
320
+ .gridui-textarea-frame:is([data-resize-handle="corner"], [data-resize-handle="edge"]) {
321
+ --_gridui-textarea-resize-height: var(--gridui-control-min-height);
192
322
  }
193
323
  }
194
324
  .gridui-input-group > input[type="search"]::-webkit-search-cancel-button {
@@ -0,0 +1,14 @@
1
+ /**
2
+ * @author WMXPY
3
+ * @package Input
4
+ * @namespace Textarea
5
+ * @description Resize Handle
6
+ */
7
+ import type { ComponentPropsWithRef, FC } from "react";
8
+ export type ResizeHandleProps = ComponentPropsWithRef<"button"> & {
9
+ readonly variant: "corner" | "edge" | "footer";
10
+ readonly label: string;
11
+ readonly controlId: string;
12
+ readonly dragging: boolean;
13
+ };
14
+ export declare const ResizeHandle: FC<ResizeHandleProps>;
@@ -5,10 +5,15 @@
5
5
  * @description Textarea
6
6
  */
7
7
  import type { TextControlOptions } from "../shared/text-control-options";
8
- import { type ComponentPropsWithRef, type FC } from "react";
8
+ import { type ComponentPropsWithRef, type FC, type ReactNode } from "react";
9
9
  export type TextareaProps = ComponentPropsWithRef<"textarea"> & TextControlOptions & {
10
10
  readonly autoSize?: boolean;
11
11
  readonly maxHeight?: number;
12
12
  readonly maxRows?: number;
13
+ /** Manual resize affordance. Automatic sizing always hides it. */
14
+ readonly resizeHandle?: "corner" | "edge" | "footer" | false;
15
+ readonly resizeLabel?: string;
16
+ /** Caller-owned footer content; footer handles share this row. */
17
+ readonly footer?: ReactNode;
13
18
  };
14
19
  export declare const Textarea: FC<TextareaProps>;
@@ -0,0 +1,10 @@
1
+ /**
2
+ * @author WMXPY
3
+ * @package Input
4
+ * @namespace Textarea
5
+ * @description Use Textarea Footer Height
6
+ */
7
+ export declare const useTextareaFooterHeight: (visible: boolean) => {
8
+ ref: import("react").RefObject<HTMLDivElement | null>;
9
+ height: number | undefined;
10
+ };
@@ -0,0 +1,19 @@
1
+ /**
2
+ * @author WMXPY
3
+ * @package Input
4
+ * @namespace Textarea
5
+ * @description Use Textarea Resize
6
+ */
7
+ import { type ComponentPropsWithRef, type CSSProperties, type RefObject } from "react";
8
+ type ResizeOptions = {
9
+ readonly frame: RefObject<HTMLDivElement | null>;
10
+ readonly input: RefObject<HTMLTextAreaElement | null>;
11
+ readonly enabled: boolean;
12
+ readonly disabled: boolean;
13
+ };
14
+ export declare const useTextareaResize: ({ frame, input, enabled, disabled }: ResizeOptions) => {
15
+ height: CSSProperties["height"];
16
+ dragging: boolean;
17
+ handleProps: ComponentPropsWithRef<"button">;
18
+ };
19
+ export {};
package/docs/catalog.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
3
  "name": "@gridui/input",
4
- "version": "3.0.1",
4
+ "version": "4.0.0",
5
5
  "category": "core",
6
6
  "description": "GridUI React input",
7
7
  "usage": "`Label` owns inline label alignment and an optional decorative `icon` slot. Icons use the standard 16px token size without an ancestor layout styling labels.",
@@ -114,10 +114,12 @@
114
114
  ],
115
115
  "examples": [
116
116
  "https://github.com/sudo-ui/GridUI-React-Monorepo/blob/main/packages-core/input/stories/addons.mdx",
117
+ "https://github.com/sudo-ui/GridUI-React-Monorepo/blob/main/packages-core/input/stories/getting-started.mdx",
117
118
  "https://github.com/sudo-ui/GridUI-React-Monorepo/blob/main/packages-core/input/stories/multiline.mdx",
118
119
  "https://github.com/sudo-ui/GridUI-React-Monorepo/blob/main/packages-core/input/stories/password-entry.mdx",
119
120
  "https://github.com/sudo-ui/GridUI-React-Monorepo/blob/main/packages-core/input/stories/search-entry.mdx",
120
121
  "https://github.com/sudo-ui/GridUI-React-Monorepo/blob/main/packages-core/input/stories/input-group.stories.tsx",
122
+ "https://github.com/sudo-ui/GridUI-React-Monorepo/blob/main/packages-core/input/stories/input.stories.tsx",
121
123
  "https://github.com/sudo-ui/GridUI-React-Monorepo/blob/main/packages-core/input/stories/password-input.stories.tsx",
122
124
  "https://github.com/sudo-ui/GridUI-React-Monorepo/blob/main/packages-core/input/stories/search-input.stories.tsx",
123
125
  "https://github.com/sudo-ui/GridUI-React-Monorepo/blob/main/packages-core/input/stories/textarea.stories.tsx"
@@ -1,7 +1,7 @@
1
1
  <!-- Generated from public Props and owned stories. Run pnpm docs:storybook. -->
2
2
  # Input
3
3
 
4
- Accepts a single line of text within a form. Use it with a visible label and field feedback to explain the expected value.
4
+ Start with a labeled native text input. Add a Form composition when submission or validation needs its own workflow.
5
5
 
6
6
  Import from ``@gridui/input`` and load Theme before this family's stylesheet.
7
7
 
@@ -13,7 +13,7 @@ import "@gridui/input/styles.css";
13
13
 
14
14
  Read the [package contract](https://github.com/sudo-ui/GridUI-React-Monorepo/blob/main/packages-core/input/README.md) for shared behavior and stylesheet dependencies.
15
15
 
16
- The [working composition](https://github.com/sudo-ui/GridUI-React-Monorepo/blob/main/storybook/scenarios/controls/input.stories.tsx) demonstrates required context, state, and related components. Use View code in Storybook to inspect the actual implementation.
16
+ The [working composition](https://github.com/sudo-ui/GridUI-React-Monorepo/blob/main/packages-core/input/stories/input.stories.tsx) demonstrates required context, state, and related components. Use View code in Storybook to inspect the actual implementation.
17
17
 
18
18
  ## API
19
19
 
@@ -87,3 +87,23 @@ Default: ``false``
87
87
 
88
88
  ``number | undefined`` · Optional
89
89
 
90
+ ### resizeHandle
91
+
92
+ ``false | "footer" | "corner" | "edge" | undefined`` · Optional
93
+
94
+ Default: ``"edge"``
95
+
96
+ Manual resize affordance. Automatic sizing always hides it.
97
+
98
+ ### resizeLabel
99
+
100
+ ``string | undefined`` · Optional
101
+
102
+ Default: ``"Resize text area"``
103
+
104
+ ### footer
105
+
106
+ ``ReactNode`` · Optional
107
+
108
+ Caller-owned footer content; footer handles share this row.
109
+
package/docs/textarea.md CHANGED
@@ -1,35 +1,74 @@
1
1
  # Textarea
2
2
 
3
- Textarea is a native multiline input. Native props, events and refs are retained,
4
- including name, form, value/defaultValue, rows, required, maxLength and ARIA.
5
- onChange runs before onValueChange. Enter and IME retain native multiline behavior.
3
+ Textarea composes a native multiline input, one structural frame, and an optional
4
+ resize handle/footer. Native props, events and refs target the textarea, including
5
+ id, name, form, value/defaultValue, rows, required, maxLength and ARIA. onChange
6
+ runs before onValueChange. Enter and IME retain native multiline behavior.
6
7
  Applications own labels, validation, persistence and submission.
7
8
 
8
- ## Automatic height
9
+ ## Resize handles
9
10
 
10
- `autoSize` uses CSS `field-sizing: content`, available in Baseline 2026 browsers
11
- (https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Properties/field-sizing).
12
- Automatic height requires support for that property. This replaces the JavaScript
13
- measurement and reset observers; there is no second sizing implementation.
14
- Ordinary non-autosizing textareas retain native rows and vertical resizing.
11
+ `resizeHandle` selects `edge` (default), `corner`, `footer`, or `false`. All variants
12
+ resize vertically and share interaction and constraints. Native browser resizing
13
+ is disabled. Read-only content can still resize; disabled controls and disabled
14
+ fieldsets cannot. `resizeLabel` names the handle for assistive technology and can
15
+ be localized; its default is `Resize text area`.
15
16
 
16
- While autosizing, `rows` (default 2) establishes the minimum through line-height
17
- and control density. Content, width, visibility, fonts, controlled updates and
18
- native resets participate in browser layout. Height is automatic; avoid a competing
19
- explicit height. Overflow remains auto, including when caller CSS limits height.
17
+ Drag the handle or focus it and use ArrowUp/ArrowDown (16px, 64px with Shift).
18
+ Home reaches the CSS minimum; End reaches a finite configured maximum. Enter,
19
+ Space, or double-click restores natural height. Escape, pointer cancellation,
20
+ capture loss, and window blur cancel an active drag and restore its prior height.
21
+ The handle is a non-submitting button; it never edits or submits the text.
22
+ Native form reset restores the text but retains a manually chosen height, just
23
+ like native textarea resizing.
20
24
 
21
- Choose either a positive finite pixel `maxHeight` or an integer `maxRows >= rows`.
22
- Supplying both throws. CSS minimum height wins when it exceeds an upper bound.
23
- The pixel prop overrides an inline style.maxHeight; without a prop the caller's
24
- CSS maximum remains effective. A caller can override generated sizing CSS through
25
- className/style, and owns the resulting geometry. Constraints do not apply while
26
- autoSize is false. Disabling autosizing restores ordinary native sizing without
27
- retained inline height or overflow mutations.
25
+ ```tsx
26
+ <Textarea aria-label="Notes" resizeHandle="corner" />
27
+ <Textarea aria-label="Notes" resizeHandle="edge" maxHeight={320} />
28
+ <Textarea aria-label="Notes" resizeHandle="footer" footer={<span>Plain text</span>} />
29
+ ```
30
+
31
+ `footer` is caller-owned content, such as a Form FieldCounter or existing buttons.
32
+ The footer handle shares its row. With corner/edge handles, supplied footer content
33
+ occupies a separate row below the handle. Without manual resizing, footer content
34
+ remains visible. No count, format selector, sending behavior, or business action
35
+ is built into Textarea. Use InputGroup for additional surrounding addons; it owns
36
+ the outer border and outline when grouped. Textarea owns its internal seams.
37
+
38
+ ## Size and automatic height
39
+
40
+ `rows` (default 2) sets the minimum text area through inherited line-height and
41
+ control density. Choose either a positive finite pixel `maxHeight` or an integer
42
+ `maxRows >= rows`. These limits apply to both manual and automatic sizing.
43
+ `maxHeight` caps the whole frame; `maxRows` caps text rows and includes space for
44
+ the handle/footer. Footer height follows its actual content, wrapping and font
45
+ metrics, so it does not consume the reserved text rows. CSS minimum height wins
46
+ over a smaller maximum. The browser resolves CSS limits, including percentages
47
+ and calc() expressions; percentage heights need a definite containing-block height.
48
+
49
+ `autoSize` uses CSS `field-sizing: content` and requires browser support for that
50
+ property. It hides the handle and clears manual height without replacing the
51
+ native input. Content, width, fonts, controlled updates and native form reset
52
+ participate in browser layout. Overflow remains auto at a maximum. Avoid a
53
+ competing fixed height when using automatic sizing.
28
54
 
29
55
  ```tsx
30
56
  <Textarea aria-label="Notes" autoSize rows={3} maxRows={8} />
31
57
  ```
32
58
 
33
- Use InputGroup with a block-start or block-end addon for a toolbar or footer.
34
- FieldCounter from Form uses native maxLength units and can be connected with
35
- aria-describedby. Composer owns sending; Textarea adds no submit shortcut.
59
+ ## Frame styling and migration
60
+
61
+ `className` and `style` now target the structural frame, including width, height,
62
+ min/max size, border, colors and typography. The native input inherits typography
63
+ and colors. The pixel maxHeight prop overrides style.maxHeight; otherwise caller
64
+ CSS limits remain effective. A manually chosen height overrides style.height until
65
+ reset or automatic sizing. Keep geometry and border overrides on the frame.
66
+ Input-specific padding, placeholder and background styling can target the native
67
+ textarea through its caller-owned id. Keep line-height on the frame so row limits
68
+ and the inherited input typography agree.
69
+
70
+ This is a DOM contract change from the previous bare textarea. Ref, id, native
71
+ attributes and events still target the actual textarea. Update direct-child CSS
72
+ selectors to use the frame or the public InputGroup composition. Browser-native
73
+ resize utilities no longer control the handle; use resizeHandle or autoSize.
74
+ Composer continues to own sending and uses automatic sizing without a handle.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gridui/input",
3
- "version": "3.0.1",
3
+ "version": "4.0.0",
4
4
  "description": "GridUI React input",
5
5
  "license": "UNLICENSED",
6
6
  "type": "module",
@@ -27,10 +27,11 @@
27
27
  "react-dom": "^19.2.0"
28
28
  },
29
29
  "dependencies": {
30
- "@gridui/button": "^4.1.0",
31
- "@gridui/overlay": "^2.0.0",
30
+ "@gridui/button": "^5.0.0",
31
+ "@gridui/interaction": "^1.2.1",
32
+ "@gridui/overlay": "^2.1.0",
32
33
  "@gridui/style-utils": "^4.0.0",
33
- "@gridui/theme": "^2.5.3",
34
+ "@gridui/theme": "^2.5.4",
34
35
  "class-variance-authority": "0.7.1",
35
36
  "lucide-react": "1.70.0"
36
37
  },