@rufous/ui 0.3.62 → 0.3.66

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/main.cjs CHANGED
@@ -2762,28 +2762,55 @@ var TextField = (0, import_react11.forwardRef)(({
2762
2762
  className
2763
2763
  ].filter(Boolean).join(" ");
2764
2764
  const internalRef = import_react11.default.useRef(null);
2765
+ const numberMin = type === "number" ? slotProps?.input?.min ?? props.min : void 0;
2766
+ const numberMax = type === "number" ? slotProps?.input?.max ?? props.max : void 0;
2767
+ const setNativeValue = (val) => {
2768
+ if (!internalRef.current) return;
2769
+ const setter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, "value")?.set;
2770
+ setter?.call(internalRef.current, val);
2771
+ };
2772
+ const emitChange = (e, numericValue) => {
2773
+ const syntheticEvent = Object.assign({}, e, {
2774
+ target: Object.assign({}, e.target, { value: numericValue }),
2775
+ currentTarget: Object.assign({}, e.currentTarget, { value: numericValue })
2776
+ });
2777
+ onChange?.(syntheticEvent);
2778
+ };
2765
2779
  const handleChange = (e) => {
2766
2780
  if (type === "number") {
2767
- const raw = e.target.value;
2768
- const inputMax = slotProps?.input?.max ?? props.max;
2781
+ let raw = e.target.value;
2769
2782
  const inputMaxLength = slotProps?.input?.maxLength ?? props.maxLength;
2770
2783
  if (inputMaxLength != null) {
2771
2784
  const digits = raw.replace(/[^0-9]/g, "");
2772
2785
  if (digits.length > Number(inputMaxLength)) return;
2773
2786
  }
2774
- if (inputMax != null && raw !== "") {
2775
- if (Number(raw) > Number(inputMax)) return;
2787
+ if (numberMax != null && raw !== "" && Number(raw) > Number(numberMax)) {
2788
+ raw = String(numberMax);
2789
+ setNativeValue(raw);
2776
2790
  }
2777
- const numericValue = raw === "" ? "" : Number(raw);
2778
- const syntheticEvent = Object.assign({}, e, {
2779
- target: Object.assign({}, e.target, { value: numericValue }),
2780
- currentTarget: Object.assign({}, e.currentTarget, { value: numericValue })
2781
- });
2782
- onChange?.(syntheticEvent);
2791
+ emitChange(e, raw === "" ? "" : Number(raw));
2783
2792
  return;
2784
2793
  }
2785
2794
  onChange?.(e);
2786
2795
  };
2796
+ const handleBlur = (e) => {
2797
+ if (type === "number") {
2798
+ const raw = e.target.value;
2799
+ if (raw !== "") {
2800
+ const n = Number(raw);
2801
+ if (!isNaN(n)) {
2802
+ let clamped = n;
2803
+ if (numberMin != null && clamped < Number(numberMin)) clamped = Number(numberMin);
2804
+ if (numberMax != null && clamped > Number(numberMax)) clamped = Number(numberMax);
2805
+ if (clamped !== n) {
2806
+ setNativeValue(String(clamped));
2807
+ emitChange(e, clamped);
2808
+ }
2809
+ }
2810
+ }
2811
+ }
2812
+ onBlur?.(e);
2813
+ };
2787
2814
  const triggerChange = () => {
2788
2815
  if (internalRef.current) {
2789
2816
  const nativeInputValueSetter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, "value")?.set;
@@ -2798,9 +2825,10 @@ var TextField = (0, import_react11.forwardRef)(({
2798
2825
  const stepBy = (delta) => {
2799
2826
  if (!internalRef.current) return;
2800
2827
  const step = Number(stepProp ?? (numberVariant ? STEP_BY_VARIANT[numberVariant] : 1));
2801
- const newVal = (parseFloat(internalRef.current.value) || 0) + delta * step;
2802
- const nativeInputValueSetter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, "value")?.set;
2803
- nativeInputValueSetter?.call(internalRef.current, String(newVal));
2828
+ let newVal = (parseFloat(internalRef.current.value) || 0) + delta * step;
2829
+ if (numberMax != null && newVal > Number(numberMax)) newVal = Number(numberMax);
2830
+ if (numberMin != null && newVal < Number(numberMin)) newVal = Number(numberMin);
2831
+ setNativeValue(String(newVal));
2804
2832
  triggerChange();
2805
2833
  };
2806
2834
  const handleIncrement = (e) => {
@@ -2872,7 +2900,8 @@ var TextField = (0, import_react11.forwardRef)(({
2872
2900
  readOnly,
2873
2901
  ...slotProps?.input,
2874
2902
  ...props,
2875
- onChange: handleChange
2903
+ onChange: handleChange,
2904
+ onBlur: handleBlur
2876
2905
  }
2877
2906
  ), InputProps?.endAdornment && /* @__PURE__ */ import_react11.default.createElement("div", { className: "rf-text-field__adornment rf-text-field__adornment--end" }, InputProps.endAdornment), !isTextarea && type === "number" && !disabled && !readOnly && /* @__PURE__ */ import_react11.default.createElement("div", { className: "rf-text-field__number-controls" }, /* @__PURE__ */ import_react11.default.createElement("button", { type: "button", tabIndex: -1, onClick: handleIncrement, className: "rf-text-field__number-btn" }, /* @__PURE__ */ import_react11.default.createElement("svg", { width: "8", height: "5", viewBox: "0 0 8 5", fill: "currentColor" }, /* @__PURE__ */ import_react11.default.createElement("path", { d: "M4 0L8 5H0L4 0Z" }))), /* @__PURE__ */ import_react11.default.createElement("button", { type: "button", tabIndex: -1, onClick: handleDecrement, className: "rf-text-field__number-btn", style: { marginTop: 2 } }, /* @__PURE__ */ import_react11.default.createElement("svg", { width: "8", height: "5", viewBox: "0 0 8 5", fill: "currentColor" }, /* @__PURE__ */ import_react11.default.createElement("path", { d: "M4 5L0 0H8L4 5Z" })))), hasLabel && /* @__PURE__ */ import_react11.default.createElement("label", { htmlFor: inputId, className: "rf-text-field__label" }, label, " ", required && /* @__PURE__ */ import_react11.default.createElement("span", { className: "rf-text-field__asterisk" }, "*")), variant === "outlined" && /* @__PURE__ */ import_react11.default.createElement("fieldset", { className: "rf-text-field__notch" }, hasLabel ? /* @__PURE__ */ import_react11.default.createElement("legend", { className: "rf-text-field__legend" }, /* @__PURE__ */ import_react11.default.createElement("span", null, label, " ", required ? "*" : "")) : /* @__PURE__ */ import_react11.default.createElement("legend", { className: "rf-text-field__legend--empty" }))), helperText && /* @__PURE__ */ import_react11.default.createElement("div", { className: "rf-text-field__helper-text" }, helperText));
2878
2907
  });
@@ -13534,6 +13563,15 @@ var IconCheck = () => /* @__PURE__ */ React197.createElement("svg", { ...s }, /*
13534
13563
  var IconPaste = () => /* @__PURE__ */ React197.createElement("svg", { ...s }, /* @__PURE__ */ React197.createElement("path", { d: "M19 2h-4.18C14.4.84 13.3 0 12 0c-1.3 0-2.4.84-2.82 2H5c-1.1 0-2 .9-2 2v16c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm-7 0c.55 0 1 .45 1 1s-.45 1-1 1-1-.45-1-1 .45-1 1-1zm7 18H5V4h2v3h10V4h2v16z" }));
13535
13564
 
13536
13565
  // lib/RufousTextEditor/Toolbar.tsx
13566
+ var BASIC_MOBILE_BUTTONS = /* @__PURE__ */ new Set([
13567
+ "undo",
13568
+ "redo",
13569
+ "bold",
13570
+ "italic",
13571
+ "link",
13572
+ "ul",
13573
+ "ol"
13574
+ ]);
13537
13575
  var COLOR_PALETTE = [
13538
13576
  // Row 1: blacks/grays
13539
13577
  "#000000",
@@ -13981,7 +14019,47 @@ var Toolbar = ({ editor, setLink, onAICommand, onTranslate, onSpeechToText, onTe
13981
14019
  const [todoEnabled, setTodoEnabled] = (0, import_react53.useState)(false);
13982
14020
  const ttsRef = (0, import_react53.useRef)(null);
13983
14021
  const sttRef = (0, import_react53.useRef)(null);
13984
- const show = (id) => !visibleButtons || visibleButtons.has(id);
14022
+ const [isMobile, setIsMobile] = (0, import_react53.useState)(false);
14023
+ const [expanded, setExpanded] = (0, import_react53.useState)(false);
14024
+ const [overflows, setOverflows] = (0, import_react53.useState)(false);
14025
+ const [winW, setWinW] = (0, import_react53.useState)(typeof window !== "undefined" ? window.innerWidth : 0);
14026
+ const rowRef = (0, import_react53.useRef)(null);
14027
+ (0, import_react53.useEffect)(() => {
14028
+ if (typeof window === "undefined" || !window.matchMedia) return;
14029
+ const mq = window.matchMedia("(max-width: 600px)");
14030
+ const update = () => setIsMobile(mq.matches);
14031
+ update();
14032
+ mq.addEventListener("change", update);
14033
+ return () => mq.removeEventListener("change", update);
14034
+ }, []);
14035
+ (0, import_react53.useEffect)(() => {
14036
+ if (typeof window === "undefined") return;
14037
+ const onResize = () => {
14038
+ setWinW(window.innerWidth);
14039
+ setOverflows(false);
14040
+ };
14041
+ window.addEventListener("resize", onResize);
14042
+ return () => window.removeEventListener("resize", onResize);
14043
+ }, []);
14044
+ const hasCollapsibleButtons = !visibleButtons ? true : [...visibleButtons].some((b) => !BASIC_MOBILE_BUTTONS.has(b));
14045
+ const collapsedActive = isMobile && overflows && hasCollapsibleButtons && !expanded;
14046
+ const show = (id) => {
14047
+ if (visibleButtons && !visibleButtons.has(id)) return false;
14048
+ if (collapsedActive && !BASIC_MOBILE_BUTTONS.has(id)) return false;
14049
+ return true;
14050
+ };
14051
+ (0, import_react53.useLayoutEffect)(() => {
14052
+ if (collapsedActive) return;
14053
+ const row = rowRef.current;
14054
+ if (!row) return;
14055
+ const kids = Array.from(row.children).filter(
14056
+ (el) => el.offsetParent !== null && !el.classList?.contains("rte-toolbar-toggle")
14057
+ );
14058
+ const firstTop = kids.length ? kids[0].offsetTop : 0;
14059
+ const wraps = kids.some((el) => el.offsetTop - firstTop > 2);
14060
+ const next = isMobile && wraps;
14061
+ setOverflows((prev) => prev === next ? prev : next);
14062
+ }, [collapsedActive, isMobile, expanded, winW, visibleButtons]);
13985
14063
  (0, import_react53.useEffect)(() => {
13986
14064
  if (!editor) return;
13987
14065
  const onTransaction = () => setEditorState((n) => n + 1);
@@ -14055,7 +14133,7 @@ var Toolbar = ({ editor, setLink, onAICommand, onTranslate, onSpeechToText, onTe
14055
14133
  setTimeout(() => setTranslateStatus(""), 2e3);
14056
14134
  }, [editor, translateSource, translateTarget, onTranslate]);
14057
14135
  if (!editor) return null;
14058
- return /* @__PURE__ */ import_react53.default.createElement("div", { className: "toolbar" }, /* @__PURE__ */ import_react53.default.createElement("div", { className: `toolbar-row ${onClose ? "with-close" : ""}` }, (show("undo") || show("redo")) && /* @__PURE__ */ import_react53.default.createElement("div", { className: "toolbar-group" }, show("undo") && /* @__PURE__ */ import_react53.default.createElement(Tooltip, { title: "Undo (Ctrl+Z)", placement: "top" }, /* @__PURE__ */ import_react53.default.createElement(
14136
+ return /* @__PURE__ */ import_react53.default.createElement("div", { className: "toolbar" }, /* @__PURE__ */ import_react53.default.createElement("div", { ref: rowRef, className: `toolbar-row ${onClose ? "with-close" : ""}` }, (show("undo") || show("redo")) && /* @__PURE__ */ import_react53.default.createElement("div", { className: "toolbar-group" }, show("undo") && /* @__PURE__ */ import_react53.default.createElement(Tooltip, { title: "Undo (Ctrl+Z)", placement: "top" }, /* @__PURE__ */ import_react53.default.createElement(
14059
14137
  "button",
14060
14138
  {
14061
14139
  className: "toolbar-btn",
@@ -14570,7 +14648,18 @@ var Toolbar = ({ editor, setLink, onAICommand, onTranslate, onSpeechToText, onTe
14570
14648
  return true;
14571
14649
  }).run();
14572
14650
  } }, /* @__PURE__ */ import_react53.default.createElement("span", { className: `task-icon task-${status}` }, /* @__PURE__ */ import_react53.default.createElement("img", { src: images[status], alt: status })), " ", labels[status]);
14573
- })))), onClose && /* @__PURE__ */ import_react53.default.createElement("div", { className: "toolbar-row-right" }, /* @__PURE__ */ import_react53.default.createElement(Tooltip, { title: "Close", placement: "top" }, /* @__PURE__ */ import_react53.default.createElement(
14651
+ }))), isMobile && overflows && hasCollapsibleButtons && /* @__PURE__ */ import_react53.default.createElement(
14652
+ "button",
14653
+ {
14654
+ type: "button",
14655
+ className: `toolbar-btn rte-toolbar-toggle ${expanded ? "is-expanded" : ""}`,
14656
+ onClick: () => setExpanded((v) => !v),
14657
+ title: expanded ? "Show fewer options" : "Show more options",
14658
+ "aria-label": expanded ? "Collapse toolbar" : "Expand toolbar",
14659
+ "aria-expanded": expanded
14660
+ },
14661
+ /* @__PURE__ */ import_react53.default.createElement(ChevronDown, { size: 18 })
14662
+ )), onClose && /* @__PURE__ */ import_react53.default.createElement("div", { className: "toolbar-row-right" }, /* @__PURE__ */ import_react53.default.createElement(Tooltip, { title: "Close", placement: "top" }, /* @__PURE__ */ import_react53.default.createElement(
14574
14663
  "button",
14575
14664
  {
14576
14665
  className: "toolbar-btn btn-cross",
package/dist/main.css CHANGED
@@ -8171,6 +8171,25 @@ pre {
8171
8171
  border-right: none;
8172
8172
  margin-right: 0;
8173
8173
  }
8174
+ .rf-rte-wrapper .toolbar-group:empty {
8175
+ display: none;
8176
+ }
8177
+ .rf-rte-wrapper .rte-toolbar-toggle {
8178
+ margin-left: auto;
8179
+ color: #a81c08;
8180
+ }
8181
+ .rf-rte-wrapper .toolbar-row.with-close .rte-toolbar-toggle {
8182
+ margin-right: 9px;
8183
+ }
8184
+ .rf-rte-wrapper .toolbar-row.with-close .rte-toolbar-toggle.is-expanded {
8185
+ margin-right: -24px;
8186
+ }
8187
+ .rf-rte-wrapper .rte-toolbar-toggle svg {
8188
+ transition: transform 0.2s ease;
8189
+ }
8190
+ .rf-rte-wrapper .rte-toolbar-toggle.is-expanded svg {
8191
+ transform: rotate(180deg);
8192
+ }
8174
8193
  .rf-rte-wrapper .toolbar-btn {
8175
8194
  display: inline-flex;
8176
8195
  align-items: center;
package/dist/main.js CHANGED
@@ -2468,28 +2468,55 @@ var TextField = forwardRef6(({
2468
2468
  className
2469
2469
  ].filter(Boolean).join(" ");
2470
2470
  const internalRef = React143.useRef(null);
2471
+ const numberMin = type === "number" ? slotProps?.input?.min ?? props.min : void 0;
2472
+ const numberMax = type === "number" ? slotProps?.input?.max ?? props.max : void 0;
2473
+ const setNativeValue = (val) => {
2474
+ if (!internalRef.current) return;
2475
+ const setter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, "value")?.set;
2476
+ setter?.call(internalRef.current, val);
2477
+ };
2478
+ const emitChange = (e, numericValue) => {
2479
+ const syntheticEvent = Object.assign({}, e, {
2480
+ target: Object.assign({}, e.target, { value: numericValue }),
2481
+ currentTarget: Object.assign({}, e.currentTarget, { value: numericValue })
2482
+ });
2483
+ onChange?.(syntheticEvent);
2484
+ };
2471
2485
  const handleChange = (e) => {
2472
2486
  if (type === "number") {
2473
- const raw = e.target.value;
2474
- const inputMax = slotProps?.input?.max ?? props.max;
2487
+ let raw = e.target.value;
2475
2488
  const inputMaxLength = slotProps?.input?.maxLength ?? props.maxLength;
2476
2489
  if (inputMaxLength != null) {
2477
2490
  const digits = raw.replace(/[^0-9]/g, "");
2478
2491
  if (digits.length > Number(inputMaxLength)) return;
2479
2492
  }
2480
- if (inputMax != null && raw !== "") {
2481
- if (Number(raw) > Number(inputMax)) return;
2493
+ if (numberMax != null && raw !== "" && Number(raw) > Number(numberMax)) {
2494
+ raw = String(numberMax);
2495
+ setNativeValue(raw);
2482
2496
  }
2483
- const numericValue = raw === "" ? "" : Number(raw);
2484
- const syntheticEvent = Object.assign({}, e, {
2485
- target: Object.assign({}, e.target, { value: numericValue }),
2486
- currentTarget: Object.assign({}, e.currentTarget, { value: numericValue })
2487
- });
2488
- onChange?.(syntheticEvent);
2497
+ emitChange(e, raw === "" ? "" : Number(raw));
2489
2498
  return;
2490
2499
  }
2491
2500
  onChange?.(e);
2492
2501
  };
2502
+ const handleBlur = (e) => {
2503
+ if (type === "number") {
2504
+ const raw = e.target.value;
2505
+ if (raw !== "") {
2506
+ const n = Number(raw);
2507
+ if (!isNaN(n)) {
2508
+ let clamped = n;
2509
+ if (numberMin != null && clamped < Number(numberMin)) clamped = Number(numberMin);
2510
+ if (numberMax != null && clamped > Number(numberMax)) clamped = Number(numberMax);
2511
+ if (clamped !== n) {
2512
+ setNativeValue(String(clamped));
2513
+ emitChange(e, clamped);
2514
+ }
2515
+ }
2516
+ }
2517
+ }
2518
+ onBlur?.(e);
2519
+ };
2493
2520
  const triggerChange = () => {
2494
2521
  if (internalRef.current) {
2495
2522
  const nativeInputValueSetter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, "value")?.set;
@@ -2504,9 +2531,10 @@ var TextField = forwardRef6(({
2504
2531
  const stepBy = (delta) => {
2505
2532
  if (!internalRef.current) return;
2506
2533
  const step = Number(stepProp ?? (numberVariant ? STEP_BY_VARIANT[numberVariant] : 1));
2507
- const newVal = (parseFloat(internalRef.current.value) || 0) + delta * step;
2508
- const nativeInputValueSetter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, "value")?.set;
2509
- nativeInputValueSetter?.call(internalRef.current, String(newVal));
2534
+ let newVal = (parseFloat(internalRef.current.value) || 0) + delta * step;
2535
+ if (numberMax != null && newVal > Number(numberMax)) newVal = Number(numberMax);
2536
+ if (numberMin != null && newVal < Number(numberMin)) newVal = Number(numberMin);
2537
+ setNativeValue(String(newVal));
2510
2538
  triggerChange();
2511
2539
  };
2512
2540
  const handleIncrement = (e) => {
@@ -2578,7 +2606,8 @@ var TextField = forwardRef6(({
2578
2606
  readOnly,
2579
2607
  ...slotProps?.input,
2580
2608
  ...props,
2581
- onChange: handleChange
2609
+ onChange: handleChange,
2610
+ onBlur: handleBlur
2582
2611
  }
2583
2612
  ), InputProps?.endAdornment && /* @__PURE__ */ React143.createElement("div", { className: "rf-text-field__adornment rf-text-field__adornment--end" }, InputProps.endAdornment), !isTextarea && type === "number" && !disabled && !readOnly && /* @__PURE__ */ React143.createElement("div", { className: "rf-text-field__number-controls" }, /* @__PURE__ */ React143.createElement("button", { type: "button", tabIndex: -1, onClick: handleIncrement, className: "rf-text-field__number-btn" }, /* @__PURE__ */ React143.createElement("svg", { width: "8", height: "5", viewBox: "0 0 8 5", fill: "currentColor" }, /* @__PURE__ */ React143.createElement("path", { d: "M4 0L8 5H0L4 0Z" }))), /* @__PURE__ */ React143.createElement("button", { type: "button", tabIndex: -1, onClick: handleDecrement, className: "rf-text-field__number-btn", style: { marginTop: 2 } }, /* @__PURE__ */ React143.createElement("svg", { width: "8", height: "5", viewBox: "0 0 8 5", fill: "currentColor" }, /* @__PURE__ */ React143.createElement("path", { d: "M4 5L0 0H8L4 5Z" })))), hasLabel && /* @__PURE__ */ React143.createElement("label", { htmlFor: inputId, className: "rf-text-field__label" }, label, " ", required && /* @__PURE__ */ React143.createElement("span", { className: "rf-text-field__asterisk" }, "*")), variant === "outlined" && /* @__PURE__ */ React143.createElement("fieldset", { className: "rf-text-field__notch" }, hasLabel ? /* @__PURE__ */ React143.createElement("legend", { className: "rf-text-field__legend" }, /* @__PURE__ */ React143.createElement("span", null, label, " ", required ? "*" : "")) : /* @__PURE__ */ React143.createElement("legend", { className: "rf-text-field__legend--empty" }))), helperText && /* @__PURE__ */ React143.createElement("div", { className: "rf-text-field__helper-text" }, helperText));
2584
2613
  });
@@ -11898,7 +11927,7 @@ function createMentionSuggestion(users) {
11898
11927
  }
11899
11928
 
11900
11929
  // lib/RufousTextEditor/Toolbar.tsx
11901
- import React198, { useState as useState34, useRef as useRef30, useEffect as useEffect27, useCallback as useCallback17 } from "react";
11930
+ import React198, { useState as useState34, useRef as useRef30, useEffect as useEffect27, useLayoutEffect as useLayoutEffect4, useCallback as useCallback17 } from "react";
11902
11931
  import { createPortal as createPortal4 } from "react-dom";
11903
11932
 
11904
11933
  // lib/RufousTextEditor/TextToSpeech.tsx
@@ -13328,6 +13357,15 @@ var IconCheck = () => /* @__PURE__ */ React197.createElement("svg", { ...s }, /*
13328
13357
  var IconPaste = () => /* @__PURE__ */ React197.createElement("svg", { ...s }, /* @__PURE__ */ React197.createElement("path", { d: "M19 2h-4.18C14.4.84 13.3 0 12 0c-1.3 0-2.4.84-2.82 2H5c-1.1 0-2 .9-2 2v16c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm-7 0c.55 0 1 .45 1 1s-.45 1-1 1-1-.45-1-1 .45-1 1-1zm7 18H5V4h2v3h10V4h2v16z" }));
13329
13358
 
13330
13359
  // lib/RufousTextEditor/Toolbar.tsx
13360
+ var BASIC_MOBILE_BUTTONS = /* @__PURE__ */ new Set([
13361
+ "undo",
13362
+ "redo",
13363
+ "bold",
13364
+ "italic",
13365
+ "link",
13366
+ "ul",
13367
+ "ol"
13368
+ ]);
13331
13369
  var COLOR_PALETTE = [
13332
13370
  // Row 1: blacks/grays
13333
13371
  "#000000",
@@ -13775,7 +13813,47 @@ var Toolbar = ({ editor, setLink, onAICommand, onTranslate, onSpeechToText, onTe
13775
13813
  const [todoEnabled, setTodoEnabled] = useState34(false);
13776
13814
  const ttsRef = useRef30(null);
13777
13815
  const sttRef = useRef30(null);
13778
- const show = (id) => !visibleButtons || visibleButtons.has(id);
13816
+ const [isMobile, setIsMobile] = useState34(false);
13817
+ const [expanded, setExpanded] = useState34(false);
13818
+ const [overflows, setOverflows] = useState34(false);
13819
+ const [winW, setWinW] = useState34(typeof window !== "undefined" ? window.innerWidth : 0);
13820
+ const rowRef = useRef30(null);
13821
+ useEffect27(() => {
13822
+ if (typeof window === "undefined" || !window.matchMedia) return;
13823
+ const mq = window.matchMedia("(max-width: 600px)");
13824
+ const update = () => setIsMobile(mq.matches);
13825
+ update();
13826
+ mq.addEventListener("change", update);
13827
+ return () => mq.removeEventListener("change", update);
13828
+ }, []);
13829
+ useEffect27(() => {
13830
+ if (typeof window === "undefined") return;
13831
+ const onResize = () => {
13832
+ setWinW(window.innerWidth);
13833
+ setOverflows(false);
13834
+ };
13835
+ window.addEventListener("resize", onResize);
13836
+ return () => window.removeEventListener("resize", onResize);
13837
+ }, []);
13838
+ const hasCollapsibleButtons = !visibleButtons ? true : [...visibleButtons].some((b) => !BASIC_MOBILE_BUTTONS.has(b));
13839
+ const collapsedActive = isMobile && overflows && hasCollapsibleButtons && !expanded;
13840
+ const show = (id) => {
13841
+ if (visibleButtons && !visibleButtons.has(id)) return false;
13842
+ if (collapsedActive && !BASIC_MOBILE_BUTTONS.has(id)) return false;
13843
+ return true;
13844
+ };
13845
+ useLayoutEffect4(() => {
13846
+ if (collapsedActive) return;
13847
+ const row = rowRef.current;
13848
+ if (!row) return;
13849
+ const kids = Array.from(row.children).filter(
13850
+ (el) => el.offsetParent !== null && !el.classList?.contains("rte-toolbar-toggle")
13851
+ );
13852
+ const firstTop = kids.length ? kids[0].offsetTop : 0;
13853
+ const wraps = kids.some((el) => el.offsetTop - firstTop > 2);
13854
+ const next = isMobile && wraps;
13855
+ setOverflows((prev) => prev === next ? prev : next);
13856
+ }, [collapsedActive, isMobile, expanded, winW, visibleButtons]);
13779
13857
  useEffect27(() => {
13780
13858
  if (!editor) return;
13781
13859
  const onTransaction = () => setEditorState((n) => n + 1);
@@ -13849,7 +13927,7 @@ var Toolbar = ({ editor, setLink, onAICommand, onTranslate, onSpeechToText, onTe
13849
13927
  setTimeout(() => setTranslateStatus(""), 2e3);
13850
13928
  }, [editor, translateSource, translateTarget, onTranslate]);
13851
13929
  if (!editor) return null;
13852
- return /* @__PURE__ */ React198.createElement("div", { className: "toolbar" }, /* @__PURE__ */ React198.createElement("div", { className: `toolbar-row ${onClose ? "with-close" : ""}` }, (show("undo") || show("redo")) && /* @__PURE__ */ React198.createElement("div", { className: "toolbar-group" }, show("undo") && /* @__PURE__ */ React198.createElement(Tooltip, { title: "Undo (Ctrl+Z)", placement: "top" }, /* @__PURE__ */ React198.createElement(
13930
+ return /* @__PURE__ */ React198.createElement("div", { className: "toolbar" }, /* @__PURE__ */ React198.createElement("div", { ref: rowRef, className: `toolbar-row ${onClose ? "with-close" : ""}` }, (show("undo") || show("redo")) && /* @__PURE__ */ React198.createElement("div", { className: "toolbar-group" }, show("undo") && /* @__PURE__ */ React198.createElement(Tooltip, { title: "Undo (Ctrl+Z)", placement: "top" }, /* @__PURE__ */ React198.createElement(
13853
13931
  "button",
13854
13932
  {
13855
13933
  className: "toolbar-btn",
@@ -14364,7 +14442,18 @@ var Toolbar = ({ editor, setLink, onAICommand, onTranslate, onSpeechToText, onTe
14364
14442
  return true;
14365
14443
  }).run();
14366
14444
  } }, /* @__PURE__ */ React198.createElement("span", { className: `task-icon task-${status}` }, /* @__PURE__ */ React198.createElement("img", { src: images[status], alt: status })), " ", labels[status]);
14367
- })))), onClose && /* @__PURE__ */ React198.createElement("div", { className: "toolbar-row-right" }, /* @__PURE__ */ React198.createElement(Tooltip, { title: "Close", placement: "top" }, /* @__PURE__ */ React198.createElement(
14445
+ }))), isMobile && overflows && hasCollapsibleButtons && /* @__PURE__ */ React198.createElement(
14446
+ "button",
14447
+ {
14448
+ type: "button",
14449
+ className: `toolbar-btn rte-toolbar-toggle ${expanded ? "is-expanded" : ""}`,
14450
+ onClick: () => setExpanded((v) => !v),
14451
+ title: expanded ? "Show fewer options" : "Show more options",
14452
+ "aria-label": expanded ? "Collapse toolbar" : "Expand toolbar",
14453
+ "aria-expanded": expanded
14454
+ },
14455
+ /* @__PURE__ */ React198.createElement(ChevronDown, { size: 18 })
14456
+ )), onClose && /* @__PURE__ */ React198.createElement("div", { className: "toolbar-row-right" }, /* @__PURE__ */ React198.createElement(Tooltip, { title: "Close", placement: "top" }, /* @__PURE__ */ React198.createElement(
14368
14457
  "button",
14369
14458
  {
14370
14459
  className: "toolbar-btn btn-cross",
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@rufous/ui",
3
3
  "private": false,
4
- "version": "0.3.62",
4
+ "version": "0.3.66",
5
5
  "type": "module",
6
6
  "description": "Experimental: A lightweight React UI component library (Beta)",
7
7
  "style": "./dist/main.css",