@mantine-vue/core 3.3.0 → 3.4.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/cjs/index.cjs CHANGED
@@ -1738,7 +1738,7 @@ function hashStyleProps(styles, media) {
1738
1738
  return `mantine-vue-${Math.abs(hash).toString(36)}`;
1739
1739
  }
1740
1740
  function useRandomClassName() {
1741
- return `mantine-vue-${Math.random().toString(36).slice(2, 9)}`;
1741
+ return `mantine-vue-${((0, vue.getCurrentInstance)() ? (0, vue.useId)() : Math.random().toString(36).slice(2, 9)).replace(/[^A-Za-z0-9_-]/g, "-")}`;
1742
1742
  }
1743
1743
  //#endregion
1744
1744
  //#region src/core/Box/style-props/style-props-data.ts
@@ -13852,13 +13852,21 @@ var leadingZerosPattern = /^-?0\d+(\.\d+)?\.?$/;
13852
13852
  var trailingZerosPattern = /\.\d*0$/;
13853
13853
  var trailingDecimalSeparatorPattern = /^-?\d+\.$/;
13854
13854
  function clamp$3(value, min, max) {
13855
- return Math.min(max ?? value, Math.max(min ?? value, value));
13855
+ return Math.min(max ?? Number.POSITIVE_INFINITY, Math.max(min ?? Number.NEGATIVE_INFINITY, value));
13856
13856
  }
13857
13857
  function clampBigInt(value, min, max) {
13858
13858
  if (min !== void 0 && value < min) return min;
13859
13859
  if (max !== void 0 && value > max) return max;
13860
13860
  return value;
13861
13861
  }
13862
+ function canStep(value) {
13863
+ if (typeof value === "number") return value < Number.MAX_SAFE_INTEGER;
13864
+ return value === "" || value !== "-" && !Number.isNaN(Number(value)) && Number(value) < Number.MAX_SAFE_INTEGER;
13865
+ }
13866
+ function canStepBigInt(value, allowNegative) {
13867
+ if (typeof value === "bigint") return true;
13868
+ return value === "" || value !== "-" && (allowNegative || !value.startsWith("-")) && /^-?\d+$/.test(value);
13869
+ }
13862
13870
  function stripFormatting(value, options) {
13863
13871
  let next = value;
13864
13872
  if (options.prefix && next.startsWith(options.prefix)) next = next.slice(options.prefix.length);
@@ -13888,9 +13896,19 @@ function parseNumberInputValue(rawValue, allowDecimal, allowNegative, allowLeadi
13888
13896
  return parsed;
13889
13897
  }
13890
13898
  function sanitizeNumberInputString(rawValue, allowDecimal, allowNegative, decimalScale) {
13891
- let normalized = rawValue;
13892
- if (!allowNegative) normalized = normalized.replace(/-/g, "");
13893
- if (!allowDecimal) return normalized.split(".")[0];
13899
+ let normalized = "";
13900
+ let hasDecimalSeparator = false;
13901
+ for (const character of rawValue) {
13902
+ if (character === "." && !allowDecimal) break;
13903
+ if (/\d/.test(character)) normalized += character;
13904
+ else if (character === "-" && allowNegative && normalized.length === 0) normalized = "-";
13905
+ else if (character === "." && allowDecimal && !hasDecimalSeparator) {
13906
+ normalized += character;
13907
+ hasDecimalSeparator = true;
13908
+ }
13909
+ }
13910
+ if (normalized.startsWith(".")) normalized = `0${normalized}`;
13911
+ else if (normalized.startsWith("-.")) normalized = `-0${normalized.slice(1)}`;
13894
13912
  if (typeof decimalScale === "number" && normalized.includes(".")) {
13895
13913
  const [integerPart, decimalPart = ""] = normalized.split(".");
13896
13914
  return `${integerPart}.${decimalPart.slice(0, decimalScale)}`;
@@ -14110,7 +14128,6 @@ var NumberInput = factory(/* @__PURE__ */ (0, vue.defineComponent)({
14110
14128
  const slots = (0, vue.useSlots)();
14111
14129
  const attrs = (0, vue.useAttrs)();
14112
14130
  const inputRef = (0, vue.ref)(null);
14113
- const isEditing = (0, vue.ref)(false);
14114
14131
  const isBigIntMode = (0, vue.computed)(() => typeof props.modelValue === "bigint" || typeof props.defaultValue === "bigint");
14115
14132
  const [value, setValue] = (0, _mantine_vue_hooks.useUncontrolled)({
14116
14133
  value: () => props.modelValue,
@@ -14143,11 +14160,7 @@ var NumberInput = factory(/* @__PURE__ */ (0, vue.defineComponent)({
14143
14160
  thousandsGroupStyle: props.thousandsGroupStyle,
14144
14161
  thousandSeparator: props.thousandSeparator
14145
14162
  }));
14146
- const getFormatterOptions = (nextValue = value.value) => ({
14147
- ...formatterOptions.value,
14148
- fixedDecimalScale: isEditing.value || typeof nextValue === "string" ? false : formatterOptions.value.fixedDecimalScale
14149
- });
14150
- const displayValue = (0, vue.computed)(() => value.value === "" || value.value === "-" ? String(value.value) : formatNumber(value.value, getFormatterOptions()));
14163
+ const displayValue = (0, vue.computed)(() => value.value === "" || value.value === "-" ? String(value.value) : formatNumber(value.value, formatterOptions.value));
14151
14164
  const clampCaretToFormatting = (input) => {
14152
14165
  const { min, max } = getCaretBoundaries(input.value, formatterOptions.value);
14153
14166
  const start = input.selectionStart;
@@ -14160,6 +14173,14 @@ var NumberInput = factory(/* @__PURE__ */ (0, vue.defineComponent)({
14160
14173
  const scheduleCaretClamp = (input) => {
14161
14174
  setTimeout(() => clampCaretToFormatting(input), 0);
14162
14175
  };
14176
+ const getFormattedCaretPosition = (value, formattedValue, position) => {
14177
+ let formattedPosition = 0;
14178
+ for (const character of value.slice(0, position)) {
14179
+ const nextPosition = formattedValue.indexOf(character, formattedPosition);
14180
+ if (nextPosition !== -1) formattedPosition = nextPosition + 1;
14181
+ }
14182
+ return formattedPosition;
14183
+ };
14163
14184
  const commitValue = (nextValue, source = "event") => {
14164
14185
  if (props.clampBehavior === "strict") {
14165
14186
  const numeric = toNumeric(nextValue);
@@ -14171,18 +14192,19 @@ var NumberInput = factory(/* @__PURE__ */ (0, vue.defineComponent)({
14171
14192
  const raw = typeof nextValue === "bigint" ? nextValue.toString() : String(nextValue);
14172
14193
  emit("value-change", {
14173
14194
  floatValue: typeof nextValue === "number" ? nextValue : Number.isNaN(Number(raw)) ? void 0 : Number(raw),
14174
- formattedValue: nextValue === "" ? "" : formatNumber(nextValue, getFormatterOptions(nextValue)),
14195
+ formattedValue: nextValue === "" ? "" : formatNumber(nextValue, formatterOptions.value),
14175
14196
  value: raw
14176
14197
  }, { source });
14177
14198
  };
14199
+ const canStepValue = (0, vue.computed)(() => isBigIntMode.value ? canStepBigInt(value.value, props.allowNegative) : canStep(value.value));
14178
14200
  const stepValue = (direction) => {
14179
- if (props.disabled || props.readOnly) return;
14201
+ if (props.disabled || props.readOnly || !canStepValue.value) return;
14180
14202
  if (isBigIntMode.value) {
14181
14203
  const current = typeof value.value === "bigint" ? value.value : BigInt(0);
14182
14204
  const step = typeof props.step === "bigint" ? props.step : BigInt(props.step);
14183
14205
  const start = typeof props.startValue === "bigint" ? props.startValue : BigInt(props.startValue);
14184
14206
  const next = (value.value === "" || value.value === "-" ? start : current) + step * BigInt(direction);
14185
- const min = typeof props.min === "bigint" ? props.min : props.min === void 0 ? void 0 : BigInt(props.min);
14207
+ const min = typeof props.min === "bigint" ? props.min : props.min === void 0 ? props.allowNegative ? void 0 : BigInt(0) : BigInt(props.min);
14186
14208
  const max = typeof props.max === "bigint" ? props.max : props.max === void 0 ? void 0 : BigInt(props.max);
14187
14209
  const clamped = clampBigInt(next, min, max);
14188
14210
  if (max !== void 0 && next > max) emit("max-reached");
@@ -14272,7 +14294,7 @@ var NumberInput = factory(/* @__PURE__ */ (0, vue.defineComponent)({
14272
14294
  onPointercancel: clearHold
14273
14295
  }, () => (0, vue.h)(NumberInputChevron, { direction: "down" }))]);
14274
14296
  /** The step controls double as the right section unless one was supplied. */
14275
- const rightSection = (0, vue.computed)(() => props.hideControls || props.readOnly ? props.rightSection : props.rightSection !== void 0 || slots.rightSection ? props.rightSection : controls);
14297
+ const rightSection = (0, vue.computed)(() => props.hideControls || props.readOnly || !canStepValue.value ? props.rightSection : props.rightSection !== void 0 || slots.rightSection ? props.rightSection : controls);
14276
14298
  const rightSectionPointerEvents = (0, vue.computed)(() => props.rightSectionPointerEvents ?? (props.disabled ? "none" : void 0));
14277
14299
  const rightSectionWidth = (0, vue.computed)(() => props.rightSectionWidth ?? `var(--ni-right-section-width-${props.size || "sm"})`);
14278
14300
  /** `bigint` values are integers only, so the numeric keypad is the right hint. */
@@ -14289,22 +14311,52 @@ var NumberInput = factory(/* @__PURE__ */ (0, vue.defineComponent)({
14289
14311
  };
14290
14312
  __expose({ rootElement });
14291
14313
  function onInput(event) {
14292
- isEditing.value = true;
14293
14314
  const input = event.currentTarget;
14294
- const rawValue = stripFormatting(input.value, formatterOptions.value);
14315
+ const inputValue = input.value;
14316
+ const selectionStart = input.selectionStart;
14317
+ const selectionEnd = input.selectionEnd;
14318
+ const rawValue = stripFormatting(inputValue, formatterOptions.value);
14295
14319
  const decimalSeparator = props.decimalSeparator || ".";
14296
- const sanitized = sanitizeNumberInputString(props.allowedDecimalSeparators.filter((separator) => separator !== decimalSeparator).reduce((acc, separator) => acc.split(separator).join("."), rawValue.replace(decimalSeparator, ".")), props.allowDecimal, props.allowNegative, props.decimalScale);
14320
+ const sanitized = sanitizeNumberInputString(props.allowedDecimalSeparators.filter((separator) => separator !== decimalSeparator).reduce((acc, separator) => acc.split(separator).join("."), rawValue.replace(decimalSeparator, ".")), isBigIntMode.value ? false : props.allowDecimal, props.allowNegative, props.decimalScale);
14297
14321
  const nextValue = isBigIntMode.value ? parseBigIntInputValue(sanitized, props.allowNegative, props.allowLeadingZeros) : parseNumberInputValue(sanitized, props.allowDecimal, props.allowNegative, props.allowLeadingZeros, props.decimalScale, props.fixedDecimalScale);
14298
14322
  if (props.clampBehavior === "strict" && !isBigIntMode.value && !isStrictAllowed(nextValue, typeof props.min === "number" ? props.min : void 0, typeof props.max === "number" ? props.max : void 0)) {
14299
14323
  restoreInputValue(input, displayValue.value);
14300
14324
  return;
14301
14325
  }
14302
- const formattedNextValue = nextValue === "" || nextValue === "-" ? String(nextValue) : formatNumber(nextValue, getFormatterOptions(nextValue));
14303
- if (input.value !== formattedNextValue) restoreInputValue(input, formattedNextValue);
14326
+ const formattedNextValue = nextValue === "" || nextValue === "-" ? String(nextValue) : formatNumber(nextValue, formatterOptions.value);
14327
+ if (input.value !== formattedNextValue) {
14328
+ restoreInputValue(input, formattedNextValue);
14329
+ if (props.fixedDecimalScale && selectionStart !== null && selectionEnd !== null) input.setSelectionRange(getFormattedCaretPosition(inputValue, formattedNextValue, selectionStart), getFormattedCaretPosition(inputValue, formattedNextValue, selectionEnd));
14330
+ }
14304
14331
  scheduleCaretClamp(input);
14305
- commitValue(nextValue);
14332
+ if (!Object.is(nextValue, value.value)) commitValue(nextValue);
14306
14333
  }
14307
14334
  function onKeydown(event) {
14335
+ const input = event.currentTarget;
14336
+ if (event.key === "Backspace" && !props.readOnly && !props.disabled && input.selectionStart !== null && input.selectionStart === input.selectionEnd) {
14337
+ if (input.selectionStart === 0) event.preventDefault();
14338
+ else if (props.fixedDecimalScale && typeof props.decimalScale === "number") {
14339
+ const decimalPosition = input.value.indexOf(props.decimalSeparator);
14340
+ const isAfterDecimalSeparator = input.value[input.selectionStart - 1] === props.decimalSeparator;
14341
+ const deletePosition = input.selectionStart - (isAfterDecimalSeparator ? 2 : 1);
14342
+ if (deletePosition >= 0 && deletePosition < decimalPosition) {
14343
+ const nextInputValue = input.value.slice(0, deletePosition) + input.value.slice(deletePosition + 1);
14344
+ const unformattedValue = stripFormatting(nextInputValue, formatterOptions.value);
14345
+ const [integerPart, decimalPart = ""] = (unformattedValue.startsWith("-") ? unformattedValue.slice(1) : unformattedValue).split(".");
14346
+ if (!/\d/.test(integerPart) && /^0*$/.test(decimalPart)) {
14347
+ event.preventDefault();
14348
+ input.value = "";
14349
+ input.setSelectionRange(0, 0);
14350
+ input.dispatchEvent(new Event("input", { bubbles: true }));
14351
+ } else if (isAfterDecimalSeparator) {
14352
+ event.preventDefault();
14353
+ input.value = nextInputValue;
14354
+ input.setSelectionRange(deletePosition, deletePosition);
14355
+ input.dispatchEvent(new Event("input", { bubbles: true }));
14356
+ }
14357
+ }
14358
+ }
14359
+ }
14308
14360
  if (!props.readOnly && props.withKeyboardEvents && event.key === "ArrowUp") {
14309
14361
  event.preventDefault();
14310
14362
  stepValue(1);
@@ -14313,7 +14365,7 @@ var NumberInput = factory(/* @__PURE__ */ (0, vue.defineComponent)({
14313
14365
  event.preventDefault();
14314
14366
  stepValue(-1);
14315
14367
  }
14316
- scheduleCaretClamp(event.currentTarget);
14368
+ scheduleCaretClamp(input);
14317
14369
  }
14318
14370
  function onKeyup(event) {
14319
14371
  scheduleCaretClamp(event.currentTarget);
@@ -14322,12 +14374,10 @@ var NumberInput = factory(/* @__PURE__ */ (0, vue.defineComponent)({
14322
14374
  scheduleCaretClamp(event.currentTarget);
14323
14375
  }
14324
14376
  function onFocus(event) {
14325
- isEditing.value = true;
14326
14377
  if (props.selectAllOnFocus) window.setTimeout(() => inputRef.value?.select(), 0);
14327
14378
  else scheduleCaretClamp(event.currentTarget);
14328
14379
  }
14329
14380
  function onBlur() {
14330
- isEditing.value = false;
14331
14381
  let sanitizedValue = value.value;
14332
14382
  const min = typeof props.min === "number" ? props.min : void 0;
14333
14383
  const max = typeof props.max === "number" ? props.max : void 0;