@radix-ng/primitives 1.1.0 → 1.1.2

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 (31) hide show
  1. package/fesm2022/radix-ng-primitives-autocomplete.mjs +66 -7
  2. package/fesm2022/radix-ng-primitives-autocomplete.mjs.map +1 -1
  3. package/fesm2022/radix-ng-primitives-checkbox.mjs +147 -125
  4. package/fesm2022/radix-ng-primitives-checkbox.mjs.map +1 -1
  5. package/fesm2022/radix-ng-primitives-combobox.mjs +171 -37
  6. package/fesm2022/radix-ng-primitives-combobox.mjs.map +1 -1
  7. package/fesm2022/radix-ng-primitives-core.mjs +173 -5
  8. package/fesm2022/radix-ng-primitives-core.mjs.map +1 -1
  9. package/fesm2022/radix-ng-primitives-number-field.mjs +38 -7
  10. package/fesm2022/radix-ng-primitives-number-field.mjs.map +1 -1
  11. package/fesm2022/radix-ng-primitives-radio.mjs +49 -2
  12. package/fesm2022/radix-ng-primitives-radio.mjs.map +1 -1
  13. package/fesm2022/radix-ng-primitives-select.mjs +35 -3
  14. package/fesm2022/radix-ng-primitives-select.mjs.map +1 -1
  15. package/fesm2022/radix-ng-primitives-slider.mjs +17 -2
  16. package/fesm2022/radix-ng-primitives-slider.mjs.map +1 -1
  17. package/fesm2022/radix-ng-primitives-switch.mjs +60 -7
  18. package/fesm2022/radix-ng-primitives-switch.mjs.map +1 -1
  19. package/fesm2022/radix-ng-primitives-toggle-group.mjs +25 -3
  20. package/fesm2022/radix-ng-primitives-toggle-group.mjs.map +1 -1
  21. package/package.json +1 -1
  22. package/types/radix-ng-primitives-autocomplete.d.ts +10 -1
  23. package/types/radix-ng-primitives-checkbox.d.ts +56 -43
  24. package/types/radix-ng-primitives-combobox.d.ts +46 -2
  25. package/types/radix-ng-primitives-core.d.ts +62 -4
  26. package/types/radix-ng-primitives-number-field.d.ts +12 -5
  27. package/types/radix-ng-primitives-radio.d.ts +6 -1
  28. package/types/radix-ng-primitives-select.d.ts +8 -1
  29. package/types/radix-ng-primitives-slider.d.ts +2 -1
  30. package/types/radix-ng-primitives-switch.d.ts +19 -3
  31. package/types/radix-ng-primitives-toggle-group.d.ts +5 -1
@@ -153,6 +153,7 @@ function injectNgControlState() {
153
153
  const touched = signal(false, { debugName: 'RdxNgControlState.touched' });
154
154
  let destroyed = false;
155
155
  let unsubscribe;
156
+ let resetControl;
156
157
  const connect = () => {
157
158
  if (destroyed) {
158
159
  return;
@@ -164,6 +165,7 @@ function injectNgControlState() {
164
165
  if (!control || typeof control.events?.subscribe !== 'function') {
165
166
  return;
166
167
  }
168
+ resetControl = (nextValue) => control.reset(nextValue);
167
169
  const sync = () => {
168
170
  name.set(ngControl.name == null ? undefined : String(ngControl.name));
169
171
  value.set(control.value);
@@ -197,7 +199,14 @@ function injectNgControlState() {
197
199
  disabled: disabled.asReadonly(),
198
200
  errors,
199
201
  dirty: dirty.asReadonly(),
200
- touched: touched.asReadonly()
202
+ touched: touched.asReadonly(),
203
+ reset: (nextValue) => {
204
+ if (!resetControl) {
205
+ return false;
206
+ }
207
+ resetControl(nextValue);
208
+ return true;
209
+ }
201
210
  };
202
211
  }
203
212
 
@@ -1987,11 +1996,30 @@ function itemToStringLabel(value) {
1987
1996
  return String(value);
1988
1997
  }
1989
1998
  /**
1990
- * Converts an item value to the string used for form serialization. Defaults to the same rules as
1991
- * {@link itemToStringLabel}; kept as a separate export so a primitive can diverge label vs. value.
1999
+ * Converts an item value to the string used for form serialization. A conventional `{ value, label }`
2000
+ * item serializes its `value` member; other non-string values use Base UI-compatible JSON serialization
2001
+ * with `String()` as a fallback. Kept separate so a primitive can diverge label vs. value.
1992
2002
  */
1993
2003
  function itemToStringValue(value) {
1994
- return itemToStringLabel(value);
2004
+ if (value !== null && typeof value === 'object' && 'value' in value && 'label' in value) {
2005
+ return serializeValue(value.value);
2006
+ }
2007
+ return serializeValue(value);
2008
+ }
2009
+ /** Base UI-compatible serialization fallback for native form values. */
2010
+ function serializeValue(value) {
2011
+ if (isNullish(value)) {
2012
+ return '';
2013
+ }
2014
+ if (typeof value === 'string') {
2015
+ return value;
2016
+ }
2017
+ try {
2018
+ return JSON.stringify(value) ?? String(value);
2019
+ }
2020
+ catch {
2021
+ return String(value);
2022
+ }
1995
2023
  }
1996
2024
  /**
1997
2025
  * Compares two item values for equality using an optional {@link ItemValueComparator}.
@@ -2364,6 +2392,14 @@ class RdxFormUiControlBase {
2364
2392
  formUiTouchTarget() {
2365
2393
  return null;
2366
2394
  }
2395
+ /**
2396
+ * Resets a same-host Reactive/template-driven control without reporting the reset as a user edit.
2397
+ * Returns `false` when no Angular `NgControl` is connected, so a control can fall back to its CVA
2398
+ * change callback for an unusually early native reset.
2399
+ */
2400
+ resetNgControl(value) {
2401
+ return this.ngControlState.reset(value);
2402
+ }
2367
2403
  /**
2368
2404
  * Reset control-owned interaction state. Angular Signal Forms calls this optional custom-control
2369
2405
  * hook from `FieldState.reset()` after restoring the field value.
@@ -3367,6 +3403,138 @@ function getActiveElement(root = document) {
3367
3403
  return activeElement;
3368
3404
  }
3369
3405
 
3406
+ /**
3407
+ * Converts a scalar or array model to HTML form values. `null` and `undefined` represent no
3408
+ * successful control; arrays deliberately become repeated entries under the same field name.
3409
+ */
3410
+ function serializeNativeFormValue(value, itemToStringValue$1 = itemToStringValue) {
3411
+ const values = Array.isArray(value) ? value : [value];
3412
+ return values.flatMap((entry) => (entry == null ? [] : [itemToStringValue$1(entry)]));
3413
+ }
3414
+ const NOOP_NATIVE_FORM_CONTROL = {
3415
+ ownerForm: () => null,
3416
+ sync: () => undefined,
3417
+ requestSubmit: () => undefined
3418
+ };
3419
+ /**
3420
+ * Adds native form semantics to a composite control, rendering hidden inputs only when the control
3421
+ * has no native successful control of its own.
3422
+ *
3423
+ * A hidden input is necessary for a headless root to enter the platform's successful-controls tree
3424
+ * (including older engines that do not dispatch `formdata` for `new FormData(form)`). One input is
3425
+ * created per serialized value, preserving repeated-field semantics; controls with their own native
3426
+ * input use this solely for reset and imperative-submit coordination.
3427
+ *
3428
+ * Generated inputs are browser-only. Rendering imperative sibling nodes on the server would put DOM
3429
+ * outside Angular's hydration graph; the client creates them after its view has been claimed instead.
3430
+ */
3431
+ function useNativeFormControl(options) {
3432
+ const host = inject(ElementRef).nativeElement;
3433
+ const ownerDocument = host.ownerDocument;
3434
+ const destroyRef = inject(DestroyRef);
3435
+ if (!ownerDocument || !isPlatformBrowser(inject(PLATFORM_ID))) {
3436
+ return NOOP_NATIVE_FORM_CONTROL;
3437
+ }
3438
+ const inputs = [];
3439
+ let defaultValue;
3440
+ let defaultValues = [];
3441
+ let hasDefaultValue = false;
3442
+ let alive = true;
3443
+ let viewClaimed = false;
3444
+ const ownerForm = () => {
3445
+ const formId = options.form();
3446
+ const candidate = formId
3447
+ ? ownerDocument.getElementById(formId)
3448
+ : typeof host.closest === 'function'
3449
+ ? host.closest('form')
3450
+ : host.parentElement?.closest('form');
3451
+ return candidate?.localName === 'form' ? candidate : null;
3452
+ };
3453
+ const sync = () => {
3454
+ options.syncNativeControl?.();
3455
+ const name = options.name();
3456
+ const form = options.form();
3457
+ const disabled = options.disabled();
3458
+ const nativeControlOwnsValue = options.hasNativeControl?.() ?? false;
3459
+ const values = options.value && options.serialize ? options.serialize(options.value()) : [];
3460
+ // During hydration Angular must claim every server-rendered sibling before an imperative input
3461
+ // can be inserted. The after-render callback below performs the first DOM synchronization.
3462
+ if (!viewClaimed) {
3463
+ return;
3464
+ }
3465
+ const shouldRender = Boolean(name && options.serialize && !nativeControlOwnsValue);
3466
+ const count = shouldRender ? values.length : 0;
3467
+ while (inputs.length > count) {
3468
+ inputs.pop()?.remove();
3469
+ }
3470
+ while (inputs.length < count) {
3471
+ const input = ownerDocument.createElement('input');
3472
+ input.type = 'hidden';
3473
+ const previous = inputs.at(-1);
3474
+ host.parentNode?.insertBefore(input, previous ? previous.nextSibling : host.nextSibling);
3475
+ inputs.push(input);
3476
+ }
3477
+ for (let index = 0; index < inputs.length; index++) {
3478
+ const input = inputs[index];
3479
+ input.name = name;
3480
+ input.value = values[index];
3481
+ input.disabled = disabled;
3482
+ if (form) {
3483
+ input.setAttribute('form', form);
3484
+ }
3485
+ else {
3486
+ input.removeAttribute('form');
3487
+ }
3488
+ // Native reset reads `defaultValue`; retain the original default rather than accidentally
3489
+ // promoting later user edits to the reset baseline.
3490
+ if (!input.hasAttribute('data-rdx-native-form-control')) {
3491
+ input.defaultValue = defaultValues[index] ?? values[index];
3492
+ input.setAttribute('data-rdx-native-form-control', '');
3493
+ }
3494
+ }
3495
+ };
3496
+ effect(() => {
3497
+ if (!hasDefaultValue && options.defaultValue) {
3498
+ defaultValue = options.defaultValue();
3499
+ defaultValues = options.serialize ? options.serialize(defaultValue) : [];
3500
+ hasDefaultValue = true;
3501
+ }
3502
+ });
3503
+ effect(sync);
3504
+ afterNextRender(() => {
3505
+ viewClaimed = true;
3506
+ untracked(sync);
3507
+ });
3508
+ const onReset = (event) => {
3509
+ if (event.target !== ownerForm() || !options.onReset || !hasDefaultValue) {
3510
+ return;
3511
+ }
3512
+ // Native controls reset after the event. Check `defaultPrevented` in the microtask so a later
3513
+ // bubble listener can still cancel the reset before the model follows it.
3514
+ queueMicrotask(() => {
3515
+ if (alive && !event.defaultPrevented) {
3516
+ untracked(() => options.onReset(defaultValue));
3517
+ }
3518
+ });
3519
+ };
3520
+ ownerDocument.addEventListener('reset', onReset, true);
3521
+ destroyRef.onDestroy(() => {
3522
+ alive = false;
3523
+ ownerDocument.removeEventListener('reset', onReset, true);
3524
+ for (const input of inputs) {
3525
+ input.remove();
3526
+ }
3527
+ });
3528
+ return {
3529
+ ownerForm,
3530
+ sync,
3531
+ requestSubmit: () => {
3532
+ untracked(sync);
3533
+ ownerForm()?.requestSubmit();
3534
+ }
3535
+ };
3536
+ }
3537
+
3370
3538
  /**
3371
3539
  * Creates a resize observer effect for element
3372
3540
  *
@@ -4444,5 +4612,5 @@ var RdxPositionAlign;
4444
4612
  * Generated bundle index. Do not edit.
4445
4613
  */
4446
4614
 
4447
- export { A, ALT, ARROW_DOWN, ARROW_LEFT, ARROW_RIGHT, ARROW_UP, ASTERISK, BACKSPACE, CAPS_LOCK, CONTROL, CTRL, DELETE, DOCS_BASE_URL, END, ENTER, ESCAPE, F1, F10, F11, F12, F2, F3, F4, F5, F6, F7, F8, F9, HOME, META, P, PAGE_DOWN, PAGE_UP, RDX_DEFAULT_VALIDATION_MODE, RDX_FIELD_VALIDITY, RDX_FLOATING_REGISTRATION, RDX_FLOATING_ROOT_CONTEXT, RDX_FLOATING_TREE, RDX_FORM_UI_STATE, RDX_INTERNAL_BACKDROP_ATTR, RDX_SCROLL_LOCKED_ATTR, RdxControlValueAccessor, RdxFloatingNode, RdxFloatingNodeRegistration, RdxFloatingRegistrationContext, RdxFloatingRootContext, RdxFloatingTree, RdxFormUiControlBase, RdxFormUiStateHost, RdxIdGenerator, RdxLiveAnnouncer, RdxPositionAlign, RdxPositionSide, RdxTriggerRegistry, SHIFT, SPACE, SPACE_CODE, TAB, TIME_GRANULARITIES, a, areAllDaysBetweenValid, clamp, createCancelableChangeEventDetails, createContent, createContext, createFloatingEvents, createFloatingRootContext, createFormUiState, createFormatter, createMonth, createMonths, docsUrl, elementSize, formUiStateContext, getActiveElement, getDaysBetween, getDaysInMonth, getDefaultDate, getDefaultTime, getLastFirstDayOfWeek, getMaxTransitionDuration, getNextLastDayOfWeek, getOptsByGranularity, getPlaceholder, getSegmentElements, getWeekNumber, handleAndDispatchCustomEvent, handleCalendarInitialFocus, hasTime, initializeSegmentValues, injectControlValueAccessor, injectDocument, injectFloatingRootContext, injectId, injectNgControlState, isAcceptableSegmentKey, isAfter, isAfterOrSame, isBefore, isBeforeOrSame, isBetween, isBetweenInclusive, isCalendarDateTime, isEqual, isItemEqualToValue, isNullish, isNumberString, isSegmentNavigationKey, isValidationRevealed, isZonedDateTime, itemToStringLabel, itemToStringValue, j, k, n, normalizeDateStep, normalizeHour12, normalizeHourCycle, p, provideExistingToken, provideFloatingRegistration, provideFloatingRootContext, provideFloatingTree, provideFormUiState, provideValueAccessor, rdxCheckLabelElement, rdxCheckTriggerElement, rdxDevError, rdxDevWarning, resetRdxDevWarnings, resizeEffect, resolveDisplayValid, resolveFloatingTree, roundToStepPrecision, segmentBuilders, setupInternalBackdrop, snapValueToStep, syncSegmentValues, syncTimeSegmentValues, toDate, useAnchoredScrollLock, useArrowNavigation, useDateField, useFilter, useGraceArea, useListHighlight, usePointerDrag, useScrollLock, useTransitionStatus, watch };
4615
+ export { A, ALT, ARROW_DOWN, ARROW_LEFT, ARROW_RIGHT, ARROW_UP, ASTERISK, BACKSPACE, CAPS_LOCK, CONTROL, CTRL, DELETE, DOCS_BASE_URL, END, ENTER, ESCAPE, F1, F10, F11, F12, F2, F3, F4, F5, F6, F7, F8, F9, HOME, META, P, PAGE_DOWN, PAGE_UP, RDX_DEFAULT_VALIDATION_MODE, RDX_FIELD_VALIDITY, RDX_FLOATING_REGISTRATION, RDX_FLOATING_ROOT_CONTEXT, RDX_FLOATING_TREE, RDX_FORM_UI_STATE, RDX_INTERNAL_BACKDROP_ATTR, RDX_SCROLL_LOCKED_ATTR, RdxControlValueAccessor, RdxFloatingNode, RdxFloatingNodeRegistration, RdxFloatingRegistrationContext, RdxFloatingRootContext, RdxFloatingTree, RdxFormUiControlBase, RdxFormUiStateHost, RdxIdGenerator, RdxLiveAnnouncer, RdxPositionAlign, RdxPositionSide, RdxTriggerRegistry, SHIFT, SPACE, SPACE_CODE, TAB, TIME_GRANULARITIES, a, areAllDaysBetweenValid, clamp, createCancelableChangeEventDetails, createContent, createContext, createFloatingEvents, createFloatingRootContext, createFormUiState, createFormatter, createMonth, createMonths, docsUrl, elementSize, formUiStateContext, getActiveElement, getDaysBetween, getDaysInMonth, getDefaultDate, getDefaultTime, getLastFirstDayOfWeek, getMaxTransitionDuration, getNextLastDayOfWeek, getOptsByGranularity, getPlaceholder, getSegmentElements, getWeekNumber, handleAndDispatchCustomEvent, handleCalendarInitialFocus, hasTime, initializeSegmentValues, injectControlValueAccessor, injectDocument, injectFloatingRootContext, injectId, injectNgControlState, isAcceptableSegmentKey, isAfter, isAfterOrSame, isBefore, isBeforeOrSame, isBetween, isBetweenInclusive, isCalendarDateTime, isEqual, isItemEqualToValue, isNullish, isNumberString, isSegmentNavigationKey, isValidationRevealed, isZonedDateTime, itemToStringLabel, itemToStringValue, j, k, n, normalizeDateStep, normalizeHour12, normalizeHourCycle, p, provideExistingToken, provideFloatingRegistration, provideFloatingRootContext, provideFloatingTree, provideFormUiState, provideValueAccessor, rdxCheckLabelElement, rdxCheckTriggerElement, rdxDevError, rdxDevWarning, resetRdxDevWarnings, resizeEffect, resolveDisplayValid, resolveFloatingTree, roundToStepPrecision, segmentBuilders, serializeNativeFormValue, setupInternalBackdrop, snapValueToStep, syncSegmentValues, syncTimeSegmentValues, toDate, useAnchoredScrollLock, useArrowNavigation, useDateField, useFilter, useGraceArea, useListHighlight, useNativeFormControl, usePointerDrag, useScrollLock, useTransitionStatus, watch };
4448
4616
  //# sourceMappingURL=radix-ng-primitives-core.mjs.map