myoperator-mcp 0.2.348 → 0.2.350

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.
Files changed (2) hide show
  1. package/dist/index.js +213 -10
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -1511,6 +1511,11 @@ export interface CreatableMultiSelectProps extends Omit<
1511
1511
  * If the raw value differs from the sanitized value, \`onInvalidCharacters\` is called.
1512
1512
  */
1513
1513
  sanitizeInput?: (raw: string) => string;
1514
+ /**
1515
+ * Applied after \`sanitizeInput\` on typed draft values (e.g. collapse spaces).
1516
+ * Does not affect invalid-character detection.
1517
+ */
1518
+ normalizeInput?: (sanitized: string) => string;
1514
1519
  /** Fired when \`sanitizeInput\` removed one or more characters from the raw input. */
1515
1520
  onInvalidCharacters?: () => void;
1516
1521
  /**
@@ -1600,6 +1605,12 @@ function isValueAlreadySelected(
1600
1605
  });
1601
1606
  }
1602
1607
 
1608
+ function restoreInputCursor(input: HTMLInputElement, cursorPosition: number) {
1609
+ window.requestAnimationFrame(() => {
1610
+ input.setSelectionRange(cursorPosition, cursorPosition);
1611
+ });
1612
+ }
1613
+
1603
1614
  const CreatableMultiSelect = React.forwardRef(
1604
1615
  (
1605
1616
  {
@@ -1617,6 +1628,7 @@ const CreatableMultiSelect = React.forwardRef(
1617
1628
  showPerItemCharacterCounter = true,
1618
1629
  triggerDisplay = "chips",
1619
1630
  sanitizeInput,
1631
+ normalizeInput,
1620
1632
  onInvalidCharacters,
1621
1633
  onValidInput,
1622
1634
  onInputValueChange,
@@ -1641,6 +1653,17 @@ const CreatableMultiSelect = React.forwardRef(
1641
1653
  maxLengthPerItem
1642
1654
  );
1643
1655
 
1656
+ const normalizeDraftValue = React.useCallback(
1657
+ (raw: string) => {
1658
+ const sanitized = sanitizeInput ? sanitizeInput(raw) : raw;
1659
+ const normalized = normalizeInput ? normalizeInput(sanitized) : sanitized;
1660
+ return maxLengthPerItem != null
1661
+ ? normalized.slice(0, maxLengthPerItem)
1662
+ : normalized;
1663
+ },
1664
+ [maxLengthPerItem, normalizeInput, sanitizeInput]
1665
+ );
1666
+
1644
1667
  const addValue = (val: string) => {
1645
1668
  const isPreset = options.some((o) => o.value === val);
1646
1669
  const afterSanitize = isPreset
@@ -1648,7 +1671,9 @@ const CreatableMultiSelect = React.forwardRef(
1648
1671
  : sanitizeInput
1649
1672
  ? sanitizeInput(val)
1650
1673
  : val;
1651
- const trimmed = afterSanitize.trim();
1674
+ const afterNormalize =
1675
+ isPreset || !normalizeInput ? afterSanitize : normalizeInput(afterSanitize);
1676
+ const trimmed = afterNormalize.trim();
1652
1677
  if (
1653
1678
  !trimmed ||
1654
1679
  isValueAlreadySelected(
@@ -1837,12 +1862,19 @@ const CreatableMultiSelect = React.forwardRef(
1837
1862
  if (raw !== sanitized) onInvalidCharacters?.();
1838
1863
  else onValidInput?.();
1839
1864
  }
1840
- const nextInput =
1841
- maxLengthPerItem != null
1842
- ? sanitized.slice(0, maxLengthPerItem)
1843
- : sanitized;
1865
+ const nextInput = normalizeDraftValue(raw);
1844
1866
  setInputValue(nextInput);
1845
1867
  onInputValueChange?.(nextInput);
1868
+
1869
+ if (nextInput !== raw) {
1870
+ const input = e.currentTarget;
1871
+ const rawCursor = input.selectionStart ?? raw.length;
1872
+ const nextCursor = Math.min(
1873
+ normalizeDraftValue(raw.slice(0, rawCursor)).length,
1874
+ nextInput.length
1875
+ );
1876
+ restoreInputCursor(input, nextCursor);
1877
+ }
1846
1878
  }}
1847
1879
  maxLength={maxLengthPerItem}
1848
1880
  onKeyDown={handleKeyDown}
@@ -2104,6 +2136,12 @@ export const creatableToneHintRowClassName = cn(
2104
2136
  "flex shrink-0 items-center justify-between gap-2 px-4 py-2"
2105
2137
  )
2106
2138
 
2139
+ function restoreInputCursor(input: HTMLInputElement, cursorPosition: number) {
2140
+ window.requestAnimationFrame(() => {
2141
+ input.setSelectionRange(cursorPosition, cursorPosition)
2142
+ })
2143
+ }
2144
+
2107
2145
  export interface CreatableSelectOption {
2108
2146
  value: string
2109
2147
  label: string
@@ -2183,6 +2221,17 @@ const CreatableSelect = React.forwardRef(
2183
2221
  // Merge forwarded ref with internal ref
2184
2222
  React.useImperativeHandle(ref, () => containerRef.current!)
2185
2223
 
2224
+ const normalizeSearchValue = React.useCallback(
2225
+ (raw: string) => {
2226
+ const sanitized = sanitizeInput ? sanitizeInput(raw) : raw
2227
+ const normalized = normalizeComboboxInput
2228
+ ? normalizeComboboxInput(sanitized)
2229
+ : sanitized
2230
+ return maxLength != null ? normalized.slice(0, maxLength) : normalized
2231
+ },
2232
+ [maxLength, normalizeComboboxInput, sanitizeInput]
2233
+ )
2234
+
2186
2235
  const selectedLabel = React.useMemo(() => {
2187
2236
  const found = options.find((o) => o.value === value)
2188
2237
  return found ? found.label : value || ""
@@ -2335,13 +2384,19 @@ const CreatableSelect = React.forwardRef(
2335
2384
  if (raw !== sanitized) onInvalidCharacters?.()
2336
2385
  else onValidInput?.()
2337
2386
  }
2338
- const next = normalizeComboboxInput
2339
- ? normalizeComboboxInput(sanitized)
2340
- : sanitized
2341
- const nextSearch =
2342
- maxLength != null ? next.slice(0, maxLength) : next
2387
+ const nextSearch = normalizeSearchValue(raw)
2343
2388
  setSearch(nextSearch)
2344
2389
  onInputValueChange?.(nextSearch)
2390
+
2391
+ if (nextSearch !== raw) {
2392
+ const input = e.currentTarget
2393
+ const rawCursor = input.selectionStart ?? raw.length
2394
+ const nextCursor = Math.min(
2395
+ normalizeSearchValue(raw.slice(0, rawCursor)).length,
2396
+ nextSearch.length
2397
+ )
2398
+ restoreInputCursor(input, nextCursor)
2399
+ }
2345
2400
  }}
2346
2401
  maxLength={maxLength}
2347
2402
  onKeyDown={handleKeyDown}
@@ -5152,6 +5207,28 @@ import { cn } from "@/lib/utils";
5152
5207
 
5153
5208
  const blockedNumberKeys = new Set(["e", "E"]);
5154
5209
  const decimalSeparatorKeys = new Set([".", ","]);
5210
+ const consecutiveSpaceInputTypes = new Set([
5211
+ "email",
5212
+ "password",
5213
+ "search",
5214
+ "tel",
5215
+ "text",
5216
+ "url",
5217
+ ]);
5218
+
5219
+ function shouldPreventConsecutiveSpacesForType(
5220
+ type: React.HTMLInputTypeAttribute | undefined
5221
+ ): boolean {
5222
+ return type == null || consecutiveSpaceInputTypes.has(type);
5223
+ }
5224
+
5225
+ function collapseConsecutiveSpaces(value: string): string {
5226
+ return value.replace(/ {2,}/g, " ");
5227
+ }
5228
+
5229
+ function getCollapsedCursorPosition(value: string, cursorPosition: number) {
5230
+ return collapseConsecutiveSpaces(value.slice(0, cursorPosition)).length;
5231
+ }
5155
5232
 
5156
5233
  /**
5157
5234
  * Input variants for different visual states
@@ -5209,6 +5286,11 @@ export interface InputProps
5209
5286
  * Same as \`decimal\` for whole-number-only fields. If both are set, this wins.
5210
5287
  */
5211
5288
  allowDecimal?: boolean;
5289
+ /**
5290
+ * Prevents inserting a second consecutive space in text-like inputs while
5291
+ * preserving the user's current cursor position. Defaults to \`true\`.
5292
+ */
5293
+ preventConsecutiveSpaces?: boolean;
5212
5294
  }
5213
5295
 
5214
5296
  const Input = React.forwardRef(
@@ -5222,11 +5304,13 @@ const Input = React.forwardRef(
5222
5304
  preventNumberExponent = true,
5223
5305
  decimal = true,
5224
5306
  allowDecimal,
5307
+ preventConsecutiveSpaces = true,
5225
5308
  onFocus,
5226
5309
  onBlur,
5227
5310
  onWheel,
5228
5311
  onKeyDown,
5229
5312
  onPaste,
5313
+ onBeforeInput,
5230
5314
  onChange,
5231
5315
  step,
5232
5316
  ...props
@@ -5239,6 +5323,8 @@ const Input = React.forwardRef(
5239
5323
  type === "number" && preventNumberExponent;
5240
5324
  const shouldBlockDecimals =
5241
5325
  type === "number" && !decimalAllowed;
5326
+ const shouldPreventConsecutiveSpaces =
5327
+ preventConsecutiveSpaces && shouldPreventConsecutiveSpacesForType(type);
5242
5328
 
5243
5329
  const inputEl = (
5244
5330
  <input
@@ -5302,6 +5388,33 @@ const Input = React.forwardRef(
5302
5388
  }
5303
5389
  onPaste?.(e);
5304
5390
  }}
5391
+ onBeforeInput={(e) => {
5392
+ onBeforeInput?.(e);
5393
+ if (!shouldPreventConsecutiveSpaces || e.defaultPrevented) {
5394
+ return;
5395
+ }
5396
+
5397
+ const nativeEvent = e.nativeEvent as InputEvent;
5398
+ if (nativeEvent.inputType !== "insertText" || nativeEvent.data !== " ") {
5399
+ return;
5400
+ }
5401
+
5402
+ const input = e.currentTarget;
5403
+ const selectionStart = input.selectionStart ?? input.value.length;
5404
+ const selectionEnd = input.selectionEnd ?? selectionStart;
5405
+ const nextValue =
5406
+ input.value.slice(0, selectionStart) +
5407
+ nativeEvent.data +
5408
+ input.value.slice(selectionEnd);
5409
+
5410
+ if (nextValue.includes(" ")) {
5411
+ e.preventDefault();
5412
+ input.setSelectionRange(selectionStart, selectionStart);
5413
+ window.requestAnimationFrame(() => {
5414
+ input.setSelectionRange(selectionStart, selectionStart);
5415
+ });
5416
+ }
5417
+ }}
5305
5418
  onChange={(e) => {
5306
5419
  if (shouldPreventNumberExponent && /[eE]/.test(e.target.value)) {
5307
5420
  return;
@@ -5309,6 +5422,21 @@ const Input = React.forwardRef(
5309
5422
  if (shouldBlockDecimals && /[.,]/.test(e.target.value)) {
5310
5423
  return;
5311
5424
  }
5425
+ if (shouldPreventConsecutiveSpaces && e.target.value.includes(" ")) {
5426
+ const input = e.currentTarget;
5427
+ const rawValue = input.value;
5428
+ const rawCursor = input.selectionStart ?? rawValue.length;
5429
+ const collapsedValue = collapseConsecutiveSpaces(rawValue);
5430
+ const nextCursor = Math.min(
5431
+ getCollapsedCursorPosition(rawValue, rawCursor),
5432
+ collapsedValue.length
5433
+ );
5434
+
5435
+ input.value = collapsedValue;
5436
+ window.requestAnimationFrame(() => {
5437
+ input.setSelectionRange(nextCursor, nextCursor);
5438
+ });
5439
+ }
5312
5440
  onChange?.(e);
5313
5441
  }}
5314
5442
  {...props}
@@ -9351,6 +9479,29 @@ import { Loader2, X } from "lucide-react";
9351
9479
 
9352
9480
  import { cn } from "@/lib/utils";
9353
9481
 
9482
+ const consecutiveSpaceInputTypes = new Set([
9483
+ "email",
9484
+ "password",
9485
+ "search",
9486
+ "tel",
9487
+ "text",
9488
+ "url",
9489
+ ]);
9490
+
9491
+ function shouldPreventConsecutiveSpacesForType(
9492
+ type: React.HTMLInputTypeAttribute | undefined
9493
+ ): boolean {
9494
+ return type == null || consecutiveSpaceInputTypes.has(type);
9495
+ }
9496
+
9497
+ function collapseConsecutiveSpaces(value: string): string {
9498
+ return value.replace(/ {2,}/g, " ");
9499
+ }
9500
+
9501
+ function getCollapsedCursorPosition(value: string, cursorPosition: number) {
9502
+ return collapseConsecutiveSpaces(value.slice(0, cursorPosition)).length;
9503
+ }
9504
+
9354
9505
  /**
9355
9506
  * TextField container variants for when icons/prefix/suffix are present
9356
9507
  */
@@ -9453,6 +9604,11 @@ export interface TextFieldProps
9453
9604
  labelClassName?: string;
9454
9605
  /** Additional class for the input container (includes prefix/suffix/icons) */
9455
9606
  inputContainerClassName?: string;
9607
+ /**
9608
+ * Prevents inserting a second consecutive space in text-like inputs while
9609
+ * preserving the user's current cursor position. Defaults to \`true\`.
9610
+ */
9611
+ preventConsecutiveSpaces?: boolean;
9456
9612
  }
9457
9613
 
9458
9614
  const TextField = React.forwardRef(
@@ -9480,10 +9636,12 @@ const TextField = React.forwardRef(
9480
9636
  value,
9481
9637
  defaultValue,
9482
9638
  onChange,
9639
+ onBeforeInput,
9483
9640
  onWheel,
9484
9641
  disabled,
9485
9642
  id,
9486
9643
  type,
9644
+ preventConsecutiveSpaces = true,
9487
9645
  ...props
9488
9646
  }: TextFieldProps,
9489
9647
  ref: React.ForwardedRef<HTMLInputElement>
@@ -9510,9 +9668,27 @@ const TextField = React.forwardRef(
9510
9668
 
9511
9669
  // Derive state from props
9512
9670
  const derivedState = error ? "error" : (state ?? "default");
9671
+ const shouldPreventConsecutiveSpaces =
9672
+ preventConsecutiveSpaces && shouldPreventConsecutiveSpacesForType(type);
9513
9673
 
9514
9674
  // Handle change for both controlled and uncontrolled
9515
9675
  const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
9676
+ if (shouldPreventConsecutiveSpaces && e.target.value.includes(" ")) {
9677
+ const input = e.currentTarget;
9678
+ const rawValue = input.value;
9679
+ const rawCursor = input.selectionStart ?? rawValue.length;
9680
+ const collapsedValue = collapseConsecutiveSpaces(rawValue);
9681
+ const nextCursor = Math.min(
9682
+ getCollapsedCursorPosition(rawValue, rawCursor),
9683
+ collapsedValue.length
9684
+ );
9685
+
9686
+ input.value = collapsedValue;
9687
+ window.requestAnimationFrame(() => {
9688
+ input.setSelectionRange(nextCursor, nextCursor);
9689
+ });
9690
+ }
9691
+
9516
9692
  if (!isControlled) {
9517
9693
  setInternalValue(e.target.value);
9518
9694
  }
@@ -9572,6 +9748,33 @@ const TextField = React.forwardRef(
9572
9748
  maxLength={maxLength}
9573
9749
  value={isControlled ? value : undefined}
9574
9750
  defaultValue={!isControlled ? defaultValue : undefined}
9751
+ onBeforeInput={(e) => {
9752
+ onBeforeInput?.(e);
9753
+ if (!shouldPreventConsecutiveSpaces || e.defaultPrevented) {
9754
+ return;
9755
+ }
9756
+
9757
+ const nativeEvent = e.nativeEvent as InputEvent;
9758
+ if (nativeEvent.inputType !== "insertText" || nativeEvent.data !== " ") {
9759
+ return;
9760
+ }
9761
+
9762
+ const input = e.currentTarget;
9763
+ const selectionStart = input.selectionStart ?? input.value.length;
9764
+ const selectionEnd = input.selectionEnd ?? selectionStart;
9765
+ const nextValue =
9766
+ input.value.slice(0, selectionStart) +
9767
+ nativeEvent.data +
9768
+ input.value.slice(selectionEnd);
9769
+
9770
+ if (nextValue.includes(" ")) {
9771
+ e.preventDefault();
9772
+ input.setSelectionRange(selectionStart, selectionStart);
9773
+ window.requestAnimationFrame(() => {
9774
+ input.setSelectionRange(selectionStart, selectionStart);
9775
+ });
9776
+ }
9777
+ }}
9575
9778
  onChange={handleChange}
9576
9779
  onWheel={
9577
9780
  type === "number"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "myoperator-mcp",
3
- "version": "0.2.348",
3
+ "version": "0.2.350",
4
4
  "description": "MCP server for myOperator UI components - enables AI assistants to access component metadata, examples, and design tokens",
5
5
  "type": "module",
6
6
  "bin": "./dist/index.js",