@multiplatform.one/forms 7.88.5 → 7.88.7
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/cjs/AdaptivePopup/index.native.js +2 -2
- package/dist/cjs/AdaptivePopup/index.native.js.map +1 -1
- package/dist/cjs/Button/IconButtonGroup.native.js.map +1 -1
- package/dist/cjs/Button/circleGlyph.native.js.map +1 -1
- package/dist/cjs/Button/iconButtonGroupContext.native.js.map +1 -1
- package/dist/cjs/Button/index.cjs +23 -7
- package/dist/cjs/Button/index.native.js +38 -9
- package/dist/cjs/Button/index.native.js.map +1 -1
- package/dist/cjs/Button/webGlass.native.js.map +1 -1
- package/dist/cjs/Chip/index.native.js.map +1 -1
- package/dist/cjs/ContextualSaveBar/UnsavedChangesDialog.native.js +1 -1
- package/dist/cjs/ContextualSaveBar/UnsavedChangesDialog.native.js.map +1 -1
- package/dist/cjs/ContextualSaveBar/clientKnownIdentity.native.js.map +1 -1
- package/dist/cjs/ContextualSaveBar/commitImmediateField.native.js.map +1 -1
- package/dist/cjs/ContextualSaveBar/index.native.js +3 -3
- package/dist/cjs/ContextualSaveBar/index.native.js.map +1 -1
- package/dist/cjs/ContextualSaveBar/useUnsavedChangesGuard.native.js.map +1 -1
- package/dist/cjs/ControlGroup/index.native.js +1 -1
- package/dist/cjs/ControlGroup/index.native.js.map +1 -1
- package/dist/cjs/DescriptionList/index.native.js +2 -2
- package/dist/cjs/DescriptionList/index.native.js.map +1 -1
- package/dist/cjs/ErrorSummary.native.js.map +1 -1
- package/dist/cjs/FieldDisplay/editMode.native.js.map +1 -1
- package/dist/cjs/FieldDisplay/formatters.native.js.map +1 -1
- package/dist/cjs/FieldDisplay/index.native.js +1 -1
- package/dist/cjs/FieldDisplay/index.native.js.map +1 -1
- package/dist/cjs/FieldGroup.native.js.map +1 -1
- package/dist/cjs/FloatingPanel/PanelHeader.native.js.map +1 -1
- package/dist/cjs/FloatingPanel/RegularPanel.native.js.map +1 -1
- package/dist/cjs/FloatingPanel/compactFace.native.js.map +1 -1
- package/dist/cjs/FloatingPanel/containingBlock.native.js.map +1 -1
- package/dist/cjs/FloatingPanel/coverRect.native.js.map +1 -1
- package/dist/cjs/FloatingPanel/echoContext.native.js.map +1 -1
- package/dist/cjs/FloatingPanel/nestedAction.native.js.map +1 -1
- package/dist/cjs/FloatingPanel/paintedBox.native.js.map +1 -1
- package/dist/cjs/FloatingPanel/panelWidth.native.js.map +1 -1
- package/dist/cjs/FloatingPanel/pointAnchor.cjs +61 -17
- package/dist/cjs/FloatingPanel/pointAnchor.native.js +62 -17
- package/dist/cjs/FloatingPanel/pointAnchor.native.js.map +1 -1
- package/dist/cjs/FloatingPanel/regularPlacement.native.js.map +1 -1
- package/dist/cjs/FloatingPanel/trailingAction.native.js +1 -1
- package/dist/cjs/FloatingPanel/trailingAction.native.js.map +1 -1
- package/dist/cjs/FloatingPanel/useFloatingPanel.native.js.map +1 -1
- package/dist/cjs/Form/index.native.js +1 -1
- package/dist/cjs/Form/index.native.js.map +1 -1
- package/dist/cjs/Form/submissionController.native.js.map +1 -1
- package/dist/cjs/Form/validateOn.native.js.map +1 -1
- package/dist/cjs/FormGrid.native.js.map +1 -1
- package/dist/cjs/FormSection.native.js.map +1 -1
- package/dist/cjs/InputParts/PasswordReveal.native.js.map +1 -1
- package/dist/cjs/InputParts/index.native.js.map +1 -1
- package/dist/cjs/InputParts/keyboardAppearance.native.js.map +1 -1
- package/dist/cjs/Label/index.native.js.map +1 -1
- package/dist/cjs/MediaPicker/base64.native.js.map +1 -1
- package/dist/cjs/MediaPicker/payload.native.js.map +1 -1
- package/dist/cjs/MediaPicker/types.native.js.map +1 -1
- package/dist/cjs/MediaPicker/useMediaPicker.native.js.map +1 -1
- package/dist/cjs/Skeleton/index.native.js.map +1 -1
- package/dist/cjs/Spinner/SpinnerGlyph.native.js.map +1 -1
- package/dist/cjs/Spinner/spinnerSize.native.js.map +1 -1
- package/dist/cjs/Table/index.native.js.map +1 -1
- package/dist/cjs/Wheel/haptic.native.js.map +1 -1
- package/dist/cjs/Wheel/index.native.js.map +1 -1
- package/dist/cjs/Wheel/wheelShared.native.js.map +1 -1
- package/dist/cjs/actionLabel.native.js.map +1 -1
- package/dist/cjs/fieldDefaults.native.js.map +1 -1
- package/dist/cjs/fieldLayout.native.js +1 -1
- package/dist/cjs/fieldLayout.native.js.map +1 -1
- package/dist/cjs/fields/Barcode/BarcodePreview.native.js.map +1 -1
- package/dist/cjs/fields/Barcode/BarcodeScanner.native.js +1 -1
- package/dist/cjs/fields/Barcode/code128.native.js.map +1 -1
- package/dist/cjs/fields/Barcode/index.native.js +2 -2
- package/dist/cjs/fields/Barcode/index.native.js.map +1 -1
- package/dist/cjs/fields/Barcode/qr.native.js.map +1 -1
- package/dist/cjs/fields/Barcode/scannerTypes.native.js.map +1 -1
- package/dist/cjs/fields/Checkbox/Checkbox.native.js +1 -1
- package/dist/cjs/fields/Checkbox/Checkbox.native.js.map +1 -1
- package/dist/cjs/fields/Checkbox/CheckboxBox.native.js.map +1 -1
- package/dist/cjs/fields/Checkbox/CheckboxCard.native.js.map +1 -1
- package/dist/cjs/fields/Checkbox/CheckboxFieldFrame.native.js.map +1 -1
- package/dist/cjs/fields/Checkbox/CheckboxGroup.native.js +1 -1
- package/dist/cjs/fields/Checkbox/CheckboxGroup.native.js.map +1 -1
- package/dist/cjs/fields/Checkbox/checkboxGroupContext.native.js.map +1 -1
- package/dist/cjs/fields/Checkbox/index.native.js.map +1 -1
- package/dist/cjs/fields/ColorPicker/SystemColorField.native.js.map +1 -1
- package/dist/cjs/fields/ColorPicker/index.native.js +2 -2
- package/dist/cjs/fields/ColorPicker/index.native.js.map +1 -1
- package/dist/cjs/fields/ColorPicker/parts.native.js +2 -2
- package/dist/cjs/fields/ColorPicker/parts.native.js.map +1 -1
- package/dist/cjs/fields/ColorPicker/systemColorWell.gnome.native.js.map +1 -1
- package/dist/cjs/fields/Combobox/authorOptions.native.js.map +1 -1
- package/dist/cjs/fields/Combobox/index.native.js +5 -5
- package/dist/cjs/fields/Combobox/index.native.js.map +1 -1
- package/dist/cjs/fields/DatePicker/Calendar.native.js.map +1 -1
- package/dist/cjs/fields/DatePicker/DateRangePicker.native.js +2 -2
- package/dist/cjs/fields/DatePicker/DateRangePicker.native.js.map +1 -1
- package/dist/cjs/fields/DatePicker/DatetimePicker.catalog.native.js +3 -3
- package/dist/cjs/fields/DatePicker/DatetimePicker.catalog.native.js.map +1 -1
- package/dist/cjs/fields/DatePicker/DatetimePicker.native.js +2 -2
- package/dist/cjs/fields/DatePicker/MonthPicker.native.js +2 -2
- package/dist/cjs/fields/DatePicker/MonthPicker.native.js.map +1 -1
- package/dist/cjs/fields/DatePicker/MultiDatePicker.native.js +2 -2
- package/dist/cjs/fields/DatePicker/MultiDatePicker.native.js.map +1 -1
- package/dist/cjs/fields/DatePicker/catalog.native.js +2 -2
- package/dist/cjs/fields/DatePicker/catalog.native.js.map +1 -1
- package/dist/cjs/fields/DatePicker/fieldValueSync.native.js.map +1 -1
- package/dist/cjs/fields/DatePicker/index.native.js +2 -2
- package/dist/cjs/fields/DatePicker/parts.native.js +2 -2
- package/dist/cjs/fields/DatePicker/parts.native.js.map +1 -1
- package/dist/cjs/fields/DatePicker/utils.native.js.map +1 -1
- package/dist/cjs/fields/Duration/DurationWheel.native.js +1 -1
- package/dist/cjs/fields/Duration/durationMath.native.js.map +1 -1
- package/dist/cjs/fields/Duration/durationWheelTypes.native.js.map +1 -1
- package/dist/cjs/fields/Duration/index.native.js +3 -3
- package/dist/cjs/fields/Duration/index.native.js.map +1 -1
- package/dist/cjs/fields/FileUpload/filePickerTypes.native.js.map +1 -1
- package/dist/cjs/fields/FileUpload/index.native.js +2 -2
- package/dist/cjs/fields/FileUpload/index.native.js.map +1 -1
- package/dist/cjs/fields/FileUpload/nativePickerLoader.native.js.map +1 -1
- package/dist/cjs/fields/Geolocation/expoMapsLoader.native.js.map +1 -1
- package/dist/cjs/fields/Geolocation/geoValue.native.js.map +1 -1
- package/dist/cjs/fields/Geolocation/geocoder.native.js.map +1 -1
- package/dist/cjs/fields/Geolocation/geocoderContext.native.js.map +1 -1
- package/dist/cjs/fields/Geolocation/index.native.js +5 -5
- package/dist/cjs/fields/Geolocation/index.native.js.map +1 -1
- package/dist/cjs/fields/Geolocation/mapMath.native.js.map +1 -1
- package/dist/cjs/fields/Geolocation/mapTypes.native.js.map +1 -1
- package/dist/cjs/fields/Geolocation/nativeSearchFocus.native.js.map +1 -1
- package/dist/cjs/fields/Geolocation/photonGeocoder.native.js.map +1 -1
- package/dist/cjs/fields/Icon/drawnSet.native.js.map +1 -1
- package/dist/cjs/fields/Icon/index.native.js +3 -3
- package/dist/cjs/fields/Icon/index.native.js.map +1 -1
- package/dist/cjs/fields/ImageField/index.native.js +2 -2
- package/dist/cjs/fields/ImageField/index.native.js.map +1 -1
- package/dist/cjs/fields/Input/index.native.js +3 -3
- package/dist/cjs/fields/Input/index.native.js.map +1 -1
- package/dist/cjs/fields/MentionInput/index.native.js +3 -3
- package/dist/cjs/fields/MentionInput/index.native.js.map +1 -1
- package/dist/cjs/fields/OTPInput/index.native.js +3 -3
- package/dist/cjs/fields/OTPInput/index.native.js.map +1 -1
- package/dist/cjs/fields/OTPInput/otpCellCss.native.js.map +1 -1
- package/dist/cjs/fields/OTPInput/otpCellEdge.native.js.map +1 -1
- package/dist/cjs/fields/PhoneInput/dialCodes.native.js.map +1 -1
- package/dist/cjs/fields/PhoneInput/index.native.js +3 -3
- package/dist/cjs/fields/PhoneInput/index.native.js.map +1 -1
- package/dist/cjs/fields/PhoneInput/phone.native.js.map +1 -1
- package/dist/cjs/fields/PhoneInput/regionNames.native.js.map +1 -1
- package/dist/cjs/fields/Progress/index.native.js +1 -1
- package/dist/cjs/fields/Progress/index.native.js.map +1 -1
- package/dist/cjs/fields/RadioGroup/index.native.js +1 -1
- package/dist/cjs/fields/RadioGroup/index.native.js.map +1 -1
- package/dist/cjs/fields/Rating/index.native.js +1 -1
- package/dist/cjs/fields/Rating/index.native.js.map +1 -1
- package/dist/cjs/fields/Rating/ratingMath.native.js.map +1 -1
- package/dist/cjs/fields/Rating/types.native.js.map +1 -1
- package/dist/cjs/fields/SearchInput/index.native.js +2 -2
- package/dist/cjs/fields/SearchInput/index.native.js.map +1 -1
- package/dist/cjs/fields/Select/InlineChoices.native.js.map +1 -1
- package/dist/cjs/fields/Select/SheetPickerRow.native.js.map +1 -1
- package/dist/cjs/fields/Select/index.native.js +4 -4
- package/dist/cjs/fields/Select/index.native.js.map +1 -1
- package/dist/cjs/fields/Select/shouldInlineChoices.native.js.map +1 -1
- package/dist/cjs/fields/Signature/index.native.js +2 -2
- package/dist/cjs/fields/Signature/index.native.js.map +1 -1
- package/dist/cjs/fields/Slider/index.native.js +1 -1
- package/dist/cjs/fields/Slider/index.native.js.map +1 -1
- package/dist/cjs/fields/Slider/jumpMotion.native.js.map +1 -1
- package/dist/cjs/fields/Stepper/index.native.js +2 -2
- package/dist/cjs/fields/Stepper/index.native.js.map +1 -1
- package/dist/cjs/fields/Stepper/resolveStepperPlacement.native.js.map +1 -1
- package/dist/cjs/fields/Switch/footprint.native.js.map +1 -1
- package/dist/cjs/fields/Switch/index.native.js +2 -2
- package/dist/cjs/fields/Switch/index.native.js.map +1 -1
- package/dist/cjs/fields/TextArea/index.native.js +2 -2
- package/dist/cjs/fields/TextArea/index.native.js.map +1 -1
- package/dist/cjs/fields/TimePicker/catalog.native.js +3 -3
- package/dist/cjs/fields/TimePicker/catalog.native.js.map +1 -1
- package/dist/cjs/fields/TimePicker/index.native.js +2 -2
- package/dist/cjs/fields/TimePicker/parts.native.js +1 -1
- package/dist/cjs/fields/TimePicker/parts.native.js.map +1 -1
- package/dist/cjs/fields/TimeRangeScrubber/geometry.native.js.map +1 -1
- package/dist/cjs/fields/TimeRangeScrubber/index.native.js +2 -2
- package/dist/cjs/fields/TimeRangeScrubber/index.native.js.map +1 -1
- package/dist/cjs/fields/ToggleGroup/index.native.js +1 -1
- package/dist/cjs/fields/ToggleGroup/index.native.js.map +1 -1
- package/dist/cjs/fields/nativeDateTime/OsDateTimeSheet.native.js +1 -1
- package/dist/cjs/fields/nativeDateTime/OsDateTimeSheet.native.js.map +1 -1
- package/dist/cjs/fields/nativeDateTime/dateTimePickerLoader.native.js.map +1 -1
- package/dist/cjs/fields/nativeDateTime/osDateTimeIosDisplay.native.js.map +1 -1
- package/dist/cjs/fields/nativeDateTime/sheetKeyboard.native.js.map +1 -1
- package/dist/cjs/fields/nativeSelect.native.js.map +1 -1
- package/dist/cjs/fields/selectedOrder.native.js.map +1 -1
- package/dist/cjs/formSpacing.native.js.map +1 -1
- package/dist/cjs/hooks/useFocusManagement.native.js.map +1 -1
- package/dist/cjs/index.native.js +45 -45
- package/dist/cjs/index.native.js.map +1 -1
- package/dist/cjs/keyboard/KeyboardDoneBar.native.js +1 -1
- package/dist/cjs/keyboard/chain.native.js.map +1 -1
- package/dist/cjs/keyboard/index.native.js.map +1 -1
- package/dist/cjs/keyboard/types.native.js.map +1 -1
- package/dist/cjs/requiredMarking.native.js.map +1 -1
- package/dist/cjs/shared/CheckGlyph.native.js.map +1 -1
- package/dist/cjs/shared/TimeColumn.native.js +1 -1
- package/dist/cjs/shared/TimeColumn.native.js.map +1 -1
- package/dist/cjs/shared/capabilityPreflight.native.js.map +1 -1
- package/dist/cjs/shared/checkedOnField.native.js.map +1 -1
- package/dist/cjs/shared/choiceRowPress.native.js.map +1 -1
- package/dist/cjs/shared/colorRamps.native.js.map +1 -1
- package/dist/cjs/shared/devWarn.native.js.map +1 -1
- package/dist/cjs/shared/fieldRowContext.native.js.map +1 -1
- package/dist/cjs/shared/floatingList.native.js +1 -1
- package/dist/cjs/shared/floatingList.native.js.map +1 -1
- package/dist/cjs/shared/focusManagement.native.js.map +1 -1
- package/dist/cjs/shared/groupContext.native.js.map +1 -1
- package/dist/cjs/shared/i18n.native.js.map +1 -1
- package/dist/cjs/shared/instantMotion.native.js.map +1 -1
- package/dist/cjs/shared/nativeA11y.native.js.map +1 -1
- package/dist/cjs/shared/nativeAutoFocus.native.js.map +1 -1
- package/dist/cjs/shared/overlaySizeClass.native.js.map +1 -1
- package/dist/cjs/shared/overlayStack.native.js.map +1 -1
- package/dist/cjs/shared/panelPortal.native.js.map +1 -1
- package/dist/cjs/shared/pickerTriggerContract.native.js.map +1 -1
- package/dist/cjs/shared/pressSlopProps.native.js.map +1 -1
- package/dist/cjs/shared/restoreRetries.native.js.map +1 -1
- package/dist/cjs/shared/sheetNavigation.native.js.map +1 -1
- package/dist/cjs/shared/systemControlModules.gnome.native.js.map +1 -1
- package/dist/cjs/shared/systemControls.native.js.map +1 -1
- package/dist/cjs/shared/t.native.js.map +1 -1
- package/dist/cjs/shared/tableCellContext.native.js.map +1 -1
- package/dist/cjs/shared/useDragScroll.native.js.map +1 -1
- package/dist/cjs/shared/useKeyboardInset.native.js.map +1 -1
- package/dist/cjs/shared/useSheetEscapeDismiss.native.js.map +1 -1
- package/dist/cjs/shared/useWheelIncrement.native.js.map +1 -1
- package/dist/cjs/shared/utils.native.js +1 -1
- package/dist/cjs/shared/utils.native.js.map +1 -1
- package/dist/cjs/shared/zIndex.native.js.map +1 -1
- package/dist/cjs/standardActions.native.js.map +1 -1
- package/dist/cjs/themedPrimitives.native.js.map +1 -1
- package/dist/cjs/types.native.js.map +1 -1
- package/dist/cjs/utils/LinkCell.native.js.map +1 -1
- package/dist/esm/Button/index.mjs +23 -7
- package/dist/esm/Button/index.mjs.map +1 -1
- package/dist/esm/Button/index.native.js +36 -7
- package/dist/esm/Button/index.native.js.map +1 -1
- package/dist/esm/FloatingPanel/pointAnchor.mjs +61 -17
- package/dist/esm/FloatingPanel/pointAnchor.mjs.map +1 -1
- package/dist/esm/FloatingPanel/pointAnchor.native.js +62 -17
- package/dist/esm/FloatingPanel/pointAnchor.native.js.map +1 -1
- package/dist/jsx/Button/index.mjs +23 -7
- package/dist/jsx/Button/index.mjs.map +1 -1
- package/dist/jsx/Button/index.native.js +36 -7
- package/dist/jsx/Button/index.native.js.map +1 -1
- package/dist/jsx/FloatingPanel/pointAnchor.mjs +61 -17
- package/dist/jsx/FloatingPanel/pointAnchor.mjs.map +1 -1
- package/dist/jsx/FloatingPanel/pointAnchor.native.js +62 -17
- package/dist/jsx/FloatingPanel/pointAnchor.native.js.map +1 -1
- package/package.json +8 -8
- package/src/Button/Button.accentInk.spec.tsx +4 -2
- package/src/Button/Button.disabledWash.spec.tsx +229 -3
- package/src/Button/Button.nativeInk.spec.tsx +61 -1
- package/src/Button/Button.outlinedAccentInk.spec.tsx +13 -1
- package/src/Button/Button.outlinedInkFlip.spec.tsx +305 -0
- package/src/Button/index.tsx +40 -6
- package/src/FloatingPanel/FloatingPanel.stories.tsx +24 -22
- package/src/FloatingPanel/pointAnchor.ts +54 -34
- package/types/Button/index.d.ts.map +1 -1
- package/types/FloatingPanel/pointAnchor.d.ts.map +1 -1
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
* house theme table of the scope that paints it.
|
|
17
17
|
*/
|
|
18
18
|
|
|
19
|
-
import { cleanup, render, screen } from "@testing-library/react";
|
|
19
|
+
import { act, cleanup, render, screen } from "@testing-library/react";
|
|
20
20
|
import {
|
|
21
21
|
aaTextContrastRatio,
|
|
22
22
|
createDefaultThemeConfig,
|
|
@@ -24,11 +24,12 @@ import {
|
|
|
24
24
|
defaultAccentTheme,
|
|
25
25
|
defaultBaseTheme,
|
|
26
26
|
defaultBuilderOptions,
|
|
27
|
+
type Knobs,
|
|
27
28
|
measureContrast,
|
|
28
29
|
normalizeToHex,
|
|
29
30
|
Preset,
|
|
30
31
|
} from "@multiplatform.one/theme";
|
|
31
|
-
import type
|
|
32
|
+
import { type ReactElement, type ReactNode, useState } from "react";
|
|
32
33
|
import { TamaguiProvider, YStack } from "tamagui";
|
|
33
34
|
import { afterEach, describe, expect, it } from "vitest";
|
|
34
35
|
import { Button, type ButtonProps } from "./index";
|
|
@@ -118,7 +119,19 @@ function painted(el: Element, prefix: string, property: string, scheme: Scheme):
|
|
|
118
119
|
(c) => c.startsWith(prefix) && !/^_[a-z]+-0(hover|active|focus|press)/.test(c),
|
|
119
120
|
);
|
|
120
121
|
if (!cls) throw new Error(`${el.tagName} carries no ${prefix} class`);
|
|
121
|
-
|
|
122
|
+
const value = injectedRuleValue(cls, property);
|
|
123
|
+
const alias = value.match(/^var\((--[\w-]+)\)$/)?.[1];
|
|
124
|
+
const declarer = alias ? declaredOn(el, alias) : null;
|
|
125
|
+
return alias && declarer
|
|
126
|
+
? resolveInScope(declarer.style.getPropertyValue(alias).trim(), scheme, scopeOf(declarer))
|
|
127
|
+
: resolveInScope(value, scheme, scopeOf(el));
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function declaredOn(el: Element, property: string): HTMLElement | null {
|
|
131
|
+
for (let node = el.parentElement; node; node = node.parentElement) {
|
|
132
|
+
if (node.style.getPropertyValue(property)) return node;
|
|
133
|
+
}
|
|
134
|
+
return null;
|
|
122
135
|
}
|
|
123
136
|
|
|
124
137
|
function opacityOf(el: Element): number {
|
|
@@ -204,3 +217,216 @@ describe("MPO-433 LC-40: keepLabel keeps the disabled label ≥ 4.5:1 on its own
|
|
|
204
217
|
}
|
|
205
218
|
}
|
|
206
219
|
});
|
|
220
|
+
|
|
221
|
+
let flipScheme: (scheme: Scheme) => void = () => {};
|
|
222
|
+
|
|
223
|
+
function SchemeRoot({ initial, children }: { initial: Scheme; children: ReactNode }) {
|
|
224
|
+
const [scheme, setScheme] = useState<Scheme>(initial);
|
|
225
|
+
flipScheme = setScheme;
|
|
226
|
+
return (
|
|
227
|
+
<TamaguiProvider config={houseConfig.tamagui} defaultTheme={scheme} disableInjectCSS>
|
|
228
|
+
{children}
|
|
229
|
+
</TamaguiProvider>
|
|
230
|
+
);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
function schemeOf(el: Element): Scheme | null {
|
|
234
|
+
for (let node = el.parentElement; node; node = node.parentElement) {
|
|
235
|
+
if (node.classList.contains("t_dark")) return "dark";
|
|
236
|
+
if (node.classList.contains("t_light")) return "light";
|
|
237
|
+
}
|
|
238
|
+
return null;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
function disabledPaint(scheme: Scheme) {
|
|
242
|
+
const label = screen.getByText("Disabled");
|
|
243
|
+
const frame = label.closest("[role=button]") as Element;
|
|
244
|
+
return {
|
|
245
|
+
scheme: schemeOf(frame),
|
|
246
|
+
fill: painted(frame, "_bg-", "background-color", scheme),
|
|
247
|
+
border: painted(frame, "_btc-", "border-top-color", scheme),
|
|
248
|
+
ink: painted(label, "_col-", "color", scheme),
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
const KNOB_SPACE = {
|
|
253
|
+
fillStyle: ["filled", "outlined"],
|
|
254
|
+
borderRadius: ["none", "small", "medium", "large", "full"],
|
|
255
|
+
borderWidth: ["none", "small", "medium", "large"],
|
|
256
|
+
elevation: ["none", "small", "medium", "large"],
|
|
257
|
+
textAccent: ["low", "medium", "high"],
|
|
258
|
+
} as const;
|
|
259
|
+
|
|
260
|
+
function knobFuzz(seed: number, count: number): Partial<Knobs>[] {
|
|
261
|
+
let state = seed;
|
|
262
|
+
const next = () => {
|
|
263
|
+
state = (state + 0x6d2b79f5) | 0;
|
|
264
|
+
let t = Math.imul(state ^ (state >>> 15), 1 | state);
|
|
265
|
+
t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;
|
|
266
|
+
return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
|
|
267
|
+
};
|
|
268
|
+
return Array.from(
|
|
269
|
+
{ length: count },
|
|
270
|
+
() =>
|
|
271
|
+
({
|
|
272
|
+
disabledStyle: "keepLabel",
|
|
273
|
+
...Object.fromEntries(
|
|
274
|
+
Object.entries(KNOB_SPACE).map(([knob, values]) => [
|
|
275
|
+
knob,
|
|
276
|
+
values[Math.floor(next() * values.length)],
|
|
277
|
+
]),
|
|
278
|
+
),
|
|
279
|
+
}) as Partial<Knobs>,
|
|
280
|
+
);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
const KNOB_COMBOS: Partial<Knobs>[] = [
|
|
284
|
+
{ disabledStyle: "keepLabel" },
|
|
285
|
+
{ disabledStyle: "dimWhole" },
|
|
286
|
+
...knobFuzz(9578, 3),
|
|
287
|
+
];
|
|
288
|
+
|
|
289
|
+
const SOLID_PALETTE = VARIANTS.filter((v) => v.name === "accent" || v.name === 'theme="accent"');
|
|
290
|
+
|
|
291
|
+
function swapSchemeClass(from: Scheme, to: Scheme) {
|
|
292
|
+
for (const el of Array.from(document.querySelectorAll(`.t_${from}`))) {
|
|
293
|
+
el.classList.replace(`t_${from}`, `t_${to}`);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
describe("FZ-9578 LC-40: a disabled solid-palette Button follows a scheme flip with no prop change", () => {
|
|
298
|
+
const FLIPS = [
|
|
299
|
+
["light", "dark"],
|
|
300
|
+
["dark", "light"],
|
|
301
|
+
] as const;
|
|
302
|
+
for (const [from, to] of FLIPS) {
|
|
303
|
+
for (const variant of SOLID_PALETTE) {
|
|
304
|
+
for (const knobs of KNOB_COMBOS) {
|
|
305
|
+
const button = (
|
|
306
|
+
<Preset overrides={knobs}>
|
|
307
|
+
<YStack backgroundColor="$background">
|
|
308
|
+
<Button {...variant.props} disabled>
|
|
309
|
+
Disabled
|
|
310
|
+
</Button>
|
|
311
|
+
</YStack>
|
|
312
|
+
</Preset>
|
|
313
|
+
);
|
|
314
|
+
const label = `${from} -> ${to} ${variant.name} ${JSON.stringify(knobs)}`;
|
|
315
|
+
|
|
316
|
+
it(`${label}: a flip that never re-renders it paints what a fresh ${to} mount paints`, () => {
|
|
317
|
+
render(<SchemeRoot initial={to}>{button}</SchemeRoot>);
|
|
318
|
+
const fresh = disabledPaint(to);
|
|
319
|
+
cleanup();
|
|
320
|
+
render(<SchemeRoot initial={from}>{button}</SchemeRoot>);
|
|
321
|
+
swapSchemeClass(from, to);
|
|
322
|
+
try {
|
|
323
|
+
expect(disabledPaint(to)).toEqual(fresh);
|
|
324
|
+
} finally {
|
|
325
|
+
swapSchemeClass(to, from);
|
|
326
|
+
}
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
it(`${label}: a provider flip paints what a fresh ${to} mount paints`, () => {
|
|
330
|
+
render(<SchemeRoot initial={to}>{button}</SchemeRoot>);
|
|
331
|
+
const fresh = disabledPaint(to);
|
|
332
|
+
cleanup();
|
|
333
|
+
render(<SchemeRoot initial={from}>{button}</SchemeRoot>);
|
|
334
|
+
act(() => flipScheme(to));
|
|
335
|
+
expect(disabledPaint(to)).toEqual(fresh);
|
|
336
|
+
});
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
describe("FZ-9578: a focused solid-palette Button keeps its node and focus when it turns disabled", () => {
|
|
343
|
+
for (const variant of SOLID_PALETTE) {
|
|
344
|
+
it(variant.name, () => {
|
|
345
|
+
const view = (disabled: boolean) => (
|
|
346
|
+
<TamaguiProvider config={houseConfig.tamagui} defaultTheme="light" disableInjectCSS>
|
|
347
|
+
<Preset overrides={{ disabledStyle: "keepLabel" }}>
|
|
348
|
+
<YStack backgroundColor="$background">
|
|
349
|
+
<Button {...variant.props} disabled={disabled}>
|
|
350
|
+
Go
|
|
351
|
+
</Button>
|
|
352
|
+
</YStack>
|
|
353
|
+
</Preset>
|
|
354
|
+
</TamaguiProvider>
|
|
355
|
+
);
|
|
356
|
+
const { rerender } = render(view(false));
|
|
357
|
+
const frame = screen.getByText("Go").closest("[role=button]") as HTMLElement;
|
|
358
|
+
frame.focus();
|
|
359
|
+
rerender(view(true));
|
|
360
|
+
expect(screen.getByText("Go").closest("[role=button]")).toBe(frame);
|
|
361
|
+
expect(document.activeElement).toBe(frame);
|
|
362
|
+
});
|
|
363
|
+
}
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
const WASH_KNOBS: Partial<Knobs> = {
|
|
367
|
+
disabledStyle: "keepLabel",
|
|
368
|
+
fillStyle: "filled",
|
|
369
|
+
borderWidth: "medium",
|
|
370
|
+
};
|
|
371
|
+
|
|
372
|
+
function washedButton(props: Partial<ButtonProps>, disabled: boolean, text: string) {
|
|
373
|
+
return (
|
|
374
|
+
<Preset overrides={WASH_KNOBS}>
|
|
375
|
+
<YStack backgroundColor="$background">
|
|
376
|
+
<Button {...props} disabled={disabled}>
|
|
377
|
+
{text}
|
|
378
|
+
</Button>
|
|
379
|
+
</YStack>
|
|
380
|
+
</Preset>
|
|
381
|
+
);
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
function hostRecipe(scheme: Scheme) {
|
|
385
|
+
const host = houseThemes[scheme] ?? {};
|
|
386
|
+
return { scheme, fill: host.color3, border: host.color6, ink: host.color11 };
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
describe("FZ-9578 LC-40: a disabled solid-palette Button washes on the host scope's $color3, $color6 and $color11", () => {
|
|
390
|
+
for (const scheme of SCHEMES) {
|
|
391
|
+
const from: Scheme = scheme === "light" ? "dark" : "light";
|
|
392
|
+
for (const variant of SOLID_PALETTE) {
|
|
393
|
+
it(`${scheme} ${variant.name}: a fresh mount`, () => {
|
|
394
|
+
render(
|
|
395
|
+
<SchemeRoot initial={scheme}>{washedButton(variant.props, true, "Disabled")}</SchemeRoot>,
|
|
396
|
+
);
|
|
397
|
+
expect(hostRecipe(scheme).fill).toMatch(/^#|^rgb|^hsl/);
|
|
398
|
+
expect(disabledPaint(scheme)).toEqual(hostRecipe(scheme));
|
|
399
|
+
});
|
|
400
|
+
|
|
401
|
+
it(`${from} -> ${scheme} ${variant.name}: a flip that never re-renders it`, () => {
|
|
402
|
+
render(
|
|
403
|
+
<SchemeRoot initial={from}>{washedButton(variant.props, true, "Disabled")}</SchemeRoot>,
|
|
404
|
+
);
|
|
405
|
+
swapSchemeClass(from, scheme);
|
|
406
|
+
try {
|
|
407
|
+
expect(disabledPaint(scheme)).toEqual(hostRecipe(scheme));
|
|
408
|
+
} finally {
|
|
409
|
+
swapSchemeClass(scheme, from);
|
|
410
|
+
}
|
|
411
|
+
});
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
});
|
|
415
|
+
|
|
416
|
+
describe("FZ-9578: the span that declares the host wash aliases is display: contents", () => {
|
|
417
|
+
for (const scheme of SCHEMES) {
|
|
418
|
+
for (const variant of SOLID_PALETTE) {
|
|
419
|
+
for (const disabled of [false, true]) {
|
|
420
|
+
it(`${scheme} ${variant.name} ${disabled ? "disabled" : "enabled"}`, () => {
|
|
421
|
+
render(
|
|
422
|
+
<SchemeRoot initial={scheme}>{washedButton(variant.props, disabled, "Go")}</SchemeRoot>,
|
|
423
|
+
);
|
|
424
|
+
const frame = screen.getByText("Go").closest("[role=button]") as Element;
|
|
425
|
+
const span = declaredOn(frame, "--mp-host-color3");
|
|
426
|
+
expect(span?.tagName).toBe("SPAN");
|
|
427
|
+
expect((span as HTMLElement).style.display).toBe("contents");
|
|
428
|
+
});
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
});
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { cleanup, render, screen } from "@testing-library/react";
|
|
2
|
-
import { createDefaultThemeConfig } from "@multiplatform.one/theme";
|
|
2
|
+
import { createDefaultThemeConfig, Preset } from "@multiplatform.one/theme";
|
|
3
3
|
import { memo } from "react";
|
|
4
4
|
import { getVariableValue, TamaguiProvider, useTheme } from "tamagui";
|
|
5
5
|
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
@@ -114,3 +114,63 @@ describe("native Button icon ink", () => {
|
|
|
114
114
|
expect(screen.getByTestId("icon").dataset.color).toBe("");
|
|
115
115
|
});
|
|
116
116
|
});
|
|
117
|
+
|
|
118
|
+
function HostProbe() {
|
|
119
|
+
const theme = useTheme();
|
|
120
|
+
return (
|
|
121
|
+
<span
|
|
122
|
+
data-testid="host"
|
|
123
|
+
data-wash={String(getVariableValue(theme.color3))}
|
|
124
|
+
data-ink={String(getVariableValue(theme.color11))}
|
|
125
|
+
/>
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function injectedRuleValue(className: string, property: string): string | undefined {
|
|
130
|
+
for (const sheet of Array.from(document.styleSheets)) {
|
|
131
|
+
for (const rule of Array.from(sheet.cssRules)) {
|
|
132
|
+
const style = (rule as CSSStyleRule).style;
|
|
133
|
+
if (!(rule as CSSStyleRule).selectorText?.endsWith(`.${className}`) || !style) continue;
|
|
134
|
+
const value = style.getPropertyValue(property);
|
|
135
|
+
if (value) return value.trim();
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
return undefined;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function frameFill(text: string): string | undefined {
|
|
142
|
+
const frame = screen.getByText(text).closest("[role=button]") as Element;
|
|
143
|
+
const cls = Array.from(frame.classList).find(
|
|
144
|
+
(c) => c.startsWith("_bg-") && !/^_bg-0(hover|active|focus|press)/.test(c),
|
|
145
|
+
);
|
|
146
|
+
return cls ? injectedRuleValue(cls, "background-color") : undefined;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
describe("FZ-9578: a native disabled accent Button keeps the host scope's literal wash", () => {
|
|
150
|
+
it.each(["light", "dark"] as const)(
|
|
151
|
+
"%s paints the host $color3 and $color11 as literals, never a CSS variable",
|
|
152
|
+
(scheme) => {
|
|
153
|
+
render(
|
|
154
|
+
<TamaguiProvider config={config.tamagui} defaultTheme={scheme} disableInjectCSS>
|
|
155
|
+
<HostProbe />
|
|
156
|
+
<Preset overrides={{ disabledStyle: "keepLabel", fillStyle: "filled" }}>
|
|
157
|
+
<Button accent disabled icon={IconProbe}>
|
|
158
|
+
Save
|
|
159
|
+
</Button>
|
|
160
|
+
<Button theme="accent" disabled icon={IconProbe}>
|
|
161
|
+
Send
|
|
162
|
+
</Button>
|
|
163
|
+
</Preset>
|
|
164
|
+
</TamaguiProvider>,
|
|
165
|
+
);
|
|
166
|
+
const host = screen.getByTestId("host").dataset;
|
|
167
|
+
expect(host.ink).toMatch(/^#|^rgb|^hsl/);
|
|
168
|
+
for (const icon of screen.getAllByTestId("icon")) {
|
|
169
|
+
expect(icon.dataset.color).toBe(host.ink);
|
|
170
|
+
}
|
|
171
|
+
for (const text of ["Save", "Send"]) {
|
|
172
|
+
expect(frameFill(text)).toBe(host.wash);
|
|
173
|
+
}
|
|
174
|
+
},
|
|
175
|
+
);
|
|
176
|
+
});
|
|
@@ -110,7 +110,19 @@ function paintedColor(el: Element, scheme: Scheme): string {
|
|
|
110
110
|
(c) => c.startsWith("_col-") && !/^_col-0(hover|active|focus|press)/.test(c),
|
|
111
111
|
);
|
|
112
112
|
if (!cls) throw new Error(`${el.tagName} carries no _col- class`);
|
|
113
|
-
|
|
113
|
+
const value = injectedRuleValue(cls, "color");
|
|
114
|
+
const alias = value.match(/^var\((--[\w-]+)\)$/)?.[1];
|
|
115
|
+
const declarer = alias ? declaredOn(el, alias) : null;
|
|
116
|
+
return alias && declarer
|
|
117
|
+
? resolveInScope(declarer.style.getPropertyValue(alias).trim(), scheme, scopeOf(declarer))
|
|
118
|
+
: resolveInScope(value, scheme, scopeOf(el));
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function declaredOn(el: Element, property: string): HTMLElement | null {
|
|
122
|
+
for (let node = el.parentElement; node; node = node.parentElement) {
|
|
123
|
+
if (node.style.getPropertyValue(property)) return node;
|
|
124
|
+
}
|
|
125
|
+
return null;
|
|
114
126
|
}
|
|
115
127
|
|
|
116
128
|
describe("MPO-447: an outlined accent Button's label reads on its host", () => {
|
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @vitest-environment jsdom
|
|
3
|
+
*
|
|
4
|
+
* FZ-9608: an outlined solid-palette Button (the `accent` prop or
|
|
5
|
+
* `theme="accent"`) inks its label on the host (MPO-447). That ink was a
|
|
6
|
+
* literal from the scheme current at render: `useAccentOnSurface()` under the
|
|
7
|
+
* default textAccent, `hostScoped()` of `$color11` under low and medium. A
|
|
8
|
+
* flip that moves only the root's t_light/t_dark class, which never
|
|
9
|
+
* re-renders the Button, left the old scheme's ink on the new page.
|
|
10
|
+
*
|
|
11
|
+
* jsdom cascades no Tamagui CSS, so this reads what each node asks for: the
|
|
12
|
+
* atomic class, the injected rule behind it, a `--mp-host-*` alias on the
|
|
13
|
+
* ancestor that declares it, and for a `var(--token)` rule the house theme
|
|
14
|
+
* table of the scope that paints it.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { act, cleanup, render, screen } from "@testing-library/react";
|
|
18
|
+
import {
|
|
19
|
+
createDefaultThemeConfig,
|
|
20
|
+
createThemesBuilder,
|
|
21
|
+
defaultAccentTheme,
|
|
22
|
+
defaultBaseTheme,
|
|
23
|
+
defaultBuilderOptions,
|
|
24
|
+
type Knobs,
|
|
25
|
+
Preset,
|
|
26
|
+
} from "@multiplatform.one/theme";
|
|
27
|
+
import { type ReactElement, type ReactNode, useState } from "react";
|
|
28
|
+
import { TamaguiProvider, YStack } from "tamagui";
|
|
29
|
+
import { afterEach, describe, expect, it } from "vitest";
|
|
30
|
+
import { Button, type ButtonProps } from "./index";
|
|
31
|
+
|
|
32
|
+
afterEach(cleanup);
|
|
33
|
+
|
|
34
|
+
const houseConfig = createDefaultThemeConfig();
|
|
35
|
+
const houseThemes = createThemesBuilder(
|
|
36
|
+
defaultBaseTheme,
|
|
37
|
+
defaultAccentTheme,
|
|
38
|
+
defaultBuilderOptions,
|
|
39
|
+
).themes() as Record<string, Record<string, string>>;
|
|
40
|
+
|
|
41
|
+
const FLIPS = [
|
|
42
|
+
["light", "dark"],
|
|
43
|
+
["dark", "light"],
|
|
44
|
+
] as const;
|
|
45
|
+
type Scheme = (typeof FLIPS)[number][number];
|
|
46
|
+
|
|
47
|
+
const VARIANTS: { name: string; props: Partial<ButtonProps> }[] = [
|
|
48
|
+
{ name: 'theme="accent" outlined', props: { theme: "accent", outlined: true } },
|
|
49
|
+
{ name: "accent outlined", props: { accent: true, outlined: true } },
|
|
50
|
+
];
|
|
51
|
+
const TEXT_ACCENTS = ["low", "medium", "high"] as const;
|
|
52
|
+
const STATES = [
|
|
53
|
+
{ name: "enabled", disabled: false },
|
|
54
|
+
{ name: "dimWhole disabled", disabled: true },
|
|
55
|
+
] as const;
|
|
56
|
+
|
|
57
|
+
function injectedRuleValue(className: string, property: string): string {
|
|
58
|
+
for (const sheet of Array.from(document.styleSheets)) {
|
|
59
|
+
for (const rule of Array.from(sheet.cssRules)) {
|
|
60
|
+
if (!(rule instanceof CSSStyleRule)) continue;
|
|
61
|
+
if (!rule.selectorText.endsWith(`.${className}`)) continue;
|
|
62
|
+
const value = rule.style.getPropertyValue(property);
|
|
63
|
+
if (value) return value.trim();
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
throw new Error(`no injected ${property} rule for .${className}`);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function scopeOf(el: Element): string | null {
|
|
70
|
+
for (let node = el.parentElement; node; node = node.parentElement) {
|
|
71
|
+
const names = Array.from(node.classList)
|
|
72
|
+
.filter((c) => c.startsWith("t_") && c !== "t_sub_theme")
|
|
73
|
+
.map((c) => c.slice(2))
|
|
74
|
+
.filter((c) => c !== "light" && c !== "dark");
|
|
75
|
+
if (names.length) return names.sort((a, b) => b.length - a.length)[0];
|
|
76
|
+
}
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function resolveInScope(value: string, scheme: Scheme, scope: string | null): string {
|
|
81
|
+
const token = value.match(/^var\(--([\w-]+)\)$/)?.[1];
|
|
82
|
+
if (!token) return value;
|
|
83
|
+
const chain: string[] = [];
|
|
84
|
+
if (scope) {
|
|
85
|
+
const parts = scope.split("_");
|
|
86
|
+
for (let i = parts.length; i > 0; i--) chain.push(`${scheme}_${parts.slice(0, i).join("_")}`);
|
|
87
|
+
}
|
|
88
|
+
chain.push(scheme);
|
|
89
|
+
for (const name of chain) {
|
|
90
|
+
const hit = houseThemes[name]?.[token];
|
|
91
|
+
if (hit) return hit;
|
|
92
|
+
}
|
|
93
|
+
throw new Error(`--${token} resolves in none of ${chain.join(", ")}`);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function declaredOn(el: Element, property: string): HTMLElement | null {
|
|
97
|
+
for (let node = el.parentElement; node; node = node.parentElement) {
|
|
98
|
+
if (node.style.getPropertyValue(property)) return node;
|
|
99
|
+
}
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/** What `el` paints for one atomic property, or null when it asks for none. */
|
|
104
|
+
function painted(el: Element, prefix: string, property: string, scheme: Scheme): string | null {
|
|
105
|
+
const cls = Array.from(el.classList).find(
|
|
106
|
+
(c) => c.startsWith(prefix) && !/^_[a-z]+-0(hover|active|focus|press)/.test(c),
|
|
107
|
+
);
|
|
108
|
+
if (!cls) return null;
|
|
109
|
+
const value = injectedRuleValue(cls, property);
|
|
110
|
+
const alias = value.match(/^var\((--[\w-]+)\)$/)?.[1];
|
|
111
|
+
const declarer = alias ? declaredOn(el, alias) : null;
|
|
112
|
+
return alias && declarer
|
|
113
|
+
? resolveInScope(declarer.style.getPropertyValue(alias).trim(), scheme, scopeOf(declarer))
|
|
114
|
+
: resolveInScope(value, scheme, scopeOf(el));
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function schemeOf(el: Element): Scheme | null {
|
|
118
|
+
for (let node = el.parentElement; node; node = node.parentElement) {
|
|
119
|
+
if (node.classList.contains("t_dark")) return "dark";
|
|
120
|
+
if (node.classList.contains("t_light")) return "light";
|
|
121
|
+
}
|
|
122
|
+
return null;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function paintOf(text: string, scheme: Scheme) {
|
|
126
|
+
const label = screen.getByText(text);
|
|
127
|
+
const frame = label.closest("[role=button]") as Element;
|
|
128
|
+
return {
|
|
129
|
+
scheme: schemeOf(frame),
|
|
130
|
+
ink: painted(label, "_col-", "color", scheme),
|
|
131
|
+
frameInk: painted(frame, "_col-", "color", scheme),
|
|
132
|
+
fill: painted(frame, "_bg-", "background-color", scheme),
|
|
133
|
+
border: painted(frame, "_btc-", "border-top-color", scheme),
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
let flipScheme: (scheme: Scheme) => void = () => {};
|
|
138
|
+
|
|
139
|
+
function SchemeRoot({ initial, children }: { initial: Scheme; children: ReactNode }) {
|
|
140
|
+
const [scheme, setScheme] = useState<Scheme>(initial);
|
|
141
|
+
flipScheme = setScheme;
|
|
142
|
+
return (
|
|
143
|
+
<TamaguiProvider config={houseConfig.tamagui} defaultTheme={scheme} disableInjectCSS>
|
|
144
|
+
{children}
|
|
145
|
+
</TamaguiProvider>
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function swapSchemeClass(from: Scheme, to: Scheme) {
|
|
150
|
+
for (const el of Array.from(document.querySelectorAll(`.t_${from}`))) {
|
|
151
|
+
el.classList.replace(`t_${from}`, `t_${to}`);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function onHost(knobs: Partial<Knobs>, button: ReactElement) {
|
|
156
|
+
return (
|
|
157
|
+
<Preset overrides={knobs}>
|
|
158
|
+
<YStack backgroundColor="$background">{button}</YStack>
|
|
159
|
+
</Preset>
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function expectFlipFollows(from: Scheme, to: Scheme, tree: ReactElement, text: string) {
|
|
164
|
+
render(<SchemeRoot initial={to}>{tree}</SchemeRoot>);
|
|
165
|
+
const fresh = paintOf(text, to);
|
|
166
|
+
cleanup();
|
|
167
|
+
render(<SchemeRoot initial={from}>{tree}</SchemeRoot>);
|
|
168
|
+
swapSchemeClass(from, to);
|
|
169
|
+
try {
|
|
170
|
+
expect(paintOf(text, to)).toEqual(fresh);
|
|
171
|
+
} finally {
|
|
172
|
+
swapSchemeClass(to, from);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function expectProviderFlipFollows(from: Scheme, to: Scheme, tree: ReactElement, text: string) {
|
|
177
|
+
render(<SchemeRoot initial={to}>{tree}</SchemeRoot>);
|
|
178
|
+
const fresh = paintOf(text, to);
|
|
179
|
+
cleanup();
|
|
180
|
+
render(<SchemeRoot initial={from}>{tree}</SchemeRoot>);
|
|
181
|
+
act(() => flipScheme(to));
|
|
182
|
+
expect(paintOf(text, to)).toEqual(fresh);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
describe("FZ-9608 MPO-447: an outlined solid-palette Button's ink follows a scheme flip", () => {
|
|
186
|
+
for (const [from, to] of FLIPS) {
|
|
187
|
+
for (const variant of VARIANTS) {
|
|
188
|
+
for (const textAccent of TEXT_ACCENTS) {
|
|
189
|
+
for (const state of STATES) {
|
|
190
|
+
const tree = onHost(
|
|
191
|
+
{ textAccent, disabledStyle: "dimWhole" },
|
|
192
|
+
<Button {...variant.props} disabled={state.disabled}>
|
|
193
|
+
Sign in
|
|
194
|
+
</Button>,
|
|
195
|
+
);
|
|
196
|
+
const label = `${from} -> ${to} ${variant.name} textAccent ${textAccent} ${state.name}`;
|
|
197
|
+
|
|
198
|
+
it(`${label}: a flip that never re-renders it paints what a fresh ${to} mount paints`, () => {
|
|
199
|
+
expectFlipFollows(from, to, tree, "Sign in");
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
it(`${label}: a provider flip paints what a fresh ${to} mount paints`, () => {
|
|
203
|
+
expectProviderFlipFollows(from, to, tree, "Sign in");
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
describe("FZ-9608 MPO-447: the default textAccent inks the label with the host's accentOnSurface", () => {
|
|
212
|
+
for (const [scheme] of FLIPS) {
|
|
213
|
+
for (const variant of VARIANTS) {
|
|
214
|
+
it(`${scheme} ${variant.name}`, () => {
|
|
215
|
+
render(
|
|
216
|
+
<SchemeRoot initial={scheme}>
|
|
217
|
+
{onHost({ textAccent: "high" }, <Button {...variant.props}>Sign in</Button>)}
|
|
218
|
+
</SchemeRoot>,
|
|
219
|
+
);
|
|
220
|
+
const host = houseThemes[scheme]?.accentOnSurface;
|
|
221
|
+
expect(host).toMatch(/^#|^rgb|^hsl/);
|
|
222
|
+
const paint = paintOf("Sign in", scheme);
|
|
223
|
+
expect(paint.ink).toBe(host);
|
|
224
|
+
expect(paint.frameInk).toBe(host);
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
const KNOB_SPACE = {
|
|
231
|
+
fillStyle: ["filled", "outlined"],
|
|
232
|
+
borderRadius: ["none", "small", "medium", "large", "full"],
|
|
233
|
+
borderWidth: ["none", "small", "medium", "large"],
|
|
234
|
+
elevation: ["none", "small", "medium", "large"],
|
|
235
|
+
textAccent: ["low", "medium", "high"],
|
|
236
|
+
disabledStyle: ["keepLabel", "dimWhole"],
|
|
237
|
+
} as const;
|
|
238
|
+
|
|
239
|
+
function knobFuzz(seed: number, count: number): Partial<Knobs>[] {
|
|
240
|
+
let state = seed;
|
|
241
|
+
const next = () => {
|
|
242
|
+
state = (state + 0x6d2b79f5) | 0;
|
|
243
|
+
let t = Math.imul(state ^ (state >>> 15), 1 | state);
|
|
244
|
+
t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;
|
|
245
|
+
return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
|
|
246
|
+
};
|
|
247
|
+
return Array.from(
|
|
248
|
+
{ length: count },
|
|
249
|
+
() =>
|
|
250
|
+
Object.fromEntries(
|
|
251
|
+
Object.entries(KNOB_SPACE).map(([knob, values]) => [
|
|
252
|
+
knob,
|
|
253
|
+
values[Math.floor(next() * values.length)],
|
|
254
|
+
]),
|
|
255
|
+
) as Partial<Knobs>,
|
|
256
|
+
);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
describe('FZ-9608: seeded knob draws of a theme="accent" Button follow a flip with no re-render', () => {
|
|
260
|
+
for (const knobs of knobFuzz(9608, 3)) {
|
|
261
|
+
for (const [from, to] of FLIPS) {
|
|
262
|
+
for (const state of STATES) {
|
|
263
|
+
it(`${from} -> ${to} ${state.name.split(" ").pop()} ${JSON.stringify(knobs)}`, () => {
|
|
264
|
+
expectFlipFollows(
|
|
265
|
+
from,
|
|
266
|
+
to,
|
|
267
|
+
onHost(
|
|
268
|
+
knobs,
|
|
269
|
+
<Button theme="accent" disabled={state.disabled}>
|
|
270
|
+
Sign in
|
|
271
|
+
</Button>,
|
|
272
|
+
),
|
|
273
|
+
"Sign in",
|
|
274
|
+
);
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
describe("FZ-9608: a focused outlined solid-palette Button keeps its node and focus", () => {
|
|
282
|
+
for (const variant of VARIANTS) {
|
|
283
|
+
it(`${variant.name}: across a scheme flip and when it turns disabled`, () => {
|
|
284
|
+
const view = (disabled: boolean) => (
|
|
285
|
+
<SchemeRoot initial="light">
|
|
286
|
+
{onHost(
|
|
287
|
+
{ disabledStyle: "dimWhole" },
|
|
288
|
+
<Button {...variant.props} disabled={disabled}>
|
|
289
|
+
Go
|
|
290
|
+
</Button>,
|
|
291
|
+
)}
|
|
292
|
+
</SchemeRoot>
|
|
293
|
+
);
|
|
294
|
+
const { rerender } = render(view(false));
|
|
295
|
+
const frame = screen.getByText("Go").closest("[role=button]") as HTMLElement;
|
|
296
|
+
frame.focus();
|
|
297
|
+
act(() => flipScheme("dark"));
|
|
298
|
+
expect(screen.getByText("Go").closest("[role=button]")).toBe(frame);
|
|
299
|
+
expect(document.activeElement).toBe(frame);
|
|
300
|
+
rerender(view(true));
|
|
301
|
+
expect(screen.getByText("Go").closest("[role=button]")).toBe(frame);
|
|
302
|
+
expect(document.activeElement).toBe(frame);
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
});
|