@platform-blocks/ui 0.5.0 → 0.6.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/lib/esm/index.js CHANGED
@@ -1164,25 +1164,50 @@ function OverlayRenderer({ style } = {}) {
1164
1164
  }) }));
1165
1165
  }
1166
1166
  function OverlayContent({ overlay, isTopmost, onBackdropPress }) {
1167
- var _a, _b, _c;
1167
+ var _a, _b, _c, _d, _e, _f;
1168
1168
  useTheme();
1169
1169
  const DEBUG = overlay.debug === true;
1170
1170
  if (DEBUG) {
1171
1171
  console.log('Rendering overlay content:');
1172
1172
  console.log('- anchor:', overlay.anchor);
1173
+ console.log('- placement:', overlay.placement);
1173
1174
  console.log('- strategy:', overlay.strategy);
1174
1175
  console.log('- zIndex:', overlay.zIndex);
1175
1176
  }
1177
+ // Check if this is a top-positioned overlay
1178
+ const isTopPlacement = (_a = overlay.placement) === null || _a === void 0 ? void 0 : _a.startsWith('top');
1179
+ // For top placements, we need to anchor from the bottom of the overlay
1180
+ // The anchor.y represents where the bottom of the overlay should be (top of the trigger minus offset)
1181
+ // So we use the estimated height to calculate where the top of the overlay would be
1176
1182
  const overlayStyle = {
1177
1183
  // Use fixed positioning on web for viewport-anchored overlays
1178
1184
  position: (Platform.OS === 'web' && overlay.strategy === 'fixed') ? 'fixed' : 'absolute',
1179
- top: ((_a = overlay.anchor) === null || _a === void 0 ? void 0 : _a.y) || 0,
1180
1185
  left: ((_b = overlay.anchor) === null || _b === void 0 ? void 0 : _b.x) || 0,
1181
1186
  zIndex: overlay.zIndex,
1182
1187
  width: overlay.width || (((_c = overlay.anchor) === null || _c === void 0 ? void 0 : _c.width) ? overlay.anchor.width : undefined),
1183
1188
  maxWidth: overlay.maxWidth,
1184
1189
  maxHeight: overlay.maxHeight,
1185
1190
  };
1191
+ if (isTopPlacement && Platform.OS === 'web') {
1192
+ // For top placements, position from the bottom of the overlay
1193
+ // anchor.y is where the top of the overlay should be, but we want to anchor from the bottom
1194
+ // so the overlay can grow upward naturally
1195
+ // The "bottom" of the anchor point is: viewport.height - (anchor.y + estimatedHeight)
1196
+ // But since we don't know actual height, use bottom anchoring relative to the trigger
1197
+ // Actually, anchor.y already accounts for the estimated height, so:
1198
+ // anchor.y = trigger.y - estimatedHeight - offset
1199
+ // We want the overlay's bottom edge to be at: trigger.y - offset
1200
+ // Which means: bottom = viewport.height - (trigger.y - offset) = viewport.height - anchor.y - estimatedHeight
1201
+ // Simpler: just set top and let it render, but the issue is the estimate is wrong
1202
+ // Better approach: use the anchor.y + anchor.height as the "bottom anchor point"
1203
+ // This is where the bottom of the overlay should be
1204
+ const bottomAnchorPoint = (((_d = overlay.anchor) === null || _d === void 0 ? void 0 : _d.y) || 0) + (((_e = overlay.anchor) === null || _e === void 0 ? void 0 : _e.height) || 0);
1205
+ overlayStyle.top = undefined;
1206
+ overlayStyle.bottom = `calc(100vh - ${bottomAnchorPoint}px)`;
1207
+ }
1208
+ else {
1209
+ overlayStyle.top = ((_f = overlay.anchor) === null || _f === void 0 ? void 0 : _f.y) || 0;
1210
+ }
1186
1211
  if (DEBUG) {
1187
1212
  console.log('- overlayStyle:', overlayStyle);
1188
1213
  }
@@ -6153,31 +6178,29 @@ const Button = (allProps) => {
6153
6178
  const { getDuration } = useReducedMotion$1();
6154
6179
  const { announce } = useAnnouncer();
6155
6180
  const { ref: focusRef} = useFocus(`button-${title || 'button'}`);
6181
+ // Track measured width for loading state preservation
6182
+ const [measuredWidth, setMeasuredWidth] = useState(null);
6183
+ const wasLoadingRef = useRef(loading);
6184
+ // When loading starts, we want to preserve the current measured width
6185
+ // When loading ends, clear the preserved width so it can resize naturally
6186
+ useEffect(() => {
6187
+ if (!loading && wasLoadingRef.current) {
6188
+ // Loading just ended, allow width to be recalculated
6189
+ setMeasuredWidth(null);
6190
+ }
6191
+ wasLoadingRef.current = loading;
6192
+ }, [loading]);
6193
+ const handleLayout = useCallback((event) => {
6194
+ // Only update measured width when not loading, so we capture the natural content width
6195
+ if (!loading) {
6196
+ const { width } = event.nativeEvent.layout;
6197
+ setMeasuredWidth(width);
6198
+ }
6199
+ // Call user's onLayout if provided
6200
+ onLayout === null || onLayout === void 0 ? void 0 : onLayout(event);
6201
+ }, [loading, onLayout]);
6156
6202
  // Determine button content - children takes precedence over title
6157
6203
  const buttonContent = children !== null && children !== void 0 ? children : title;
6158
- // Calculate minimum width for loading state based on content and size
6159
- const calculateMinWidth = () => {
6160
- if (!buttonContent || typeof buttonContent !== 'string')
6161
- return undefined;
6162
- // Base character width estimates based on size
6163
- const charWidthBySize = {
6164
- xs: 6,
6165
- sm: 7,
6166
- md: 8,
6167
- lg: 9,
6168
- xl: 10,
6169
- '2xl': 11,
6170
- '3xl': 12
6171
- };
6172
- const sizeKey = typeof size === 'string' ? size : 'md';
6173
- const charWidth = charWidthBySize[sizeKey] || 8;
6174
- const horizontalPadding = getSpacing(size) * 2; // Left + right padding
6175
- // Estimate content width: character count * average char width + padding
6176
- const contentWidth = buttonContent.length * charWidth + horizontalPadding;
6177
- // Add space for loader and gap when loading
6178
- const loaderWidth = getFontSize$1(size) + getSpacing(size) / 2; // Loader + margin
6179
- return Math.max(contentWidth, contentWidth + loaderWidth);
6180
- };
6181
6204
  // Determine what content to show based on loading state
6182
6205
  const displayContent = loading
6183
6206
  ? (loadingTitle !== undefined ? loadingTitle : '')
@@ -6216,12 +6239,12 @@ const Button = (allProps) => {
6216
6239
  const shadowStyles = getShadowStyles({ shadow: effectiveShadow }, theme, 'button');
6217
6240
  const spacingStyles = getSpacingStyles(spacingProps);
6218
6241
  const baseLayoutStyles = getLayoutStyles(layoutProps);
6219
- // Apply minimum width when loading to prevent size changes
6220
- const calculatedMinWidth = calculateMinWidth();
6242
+ // Apply measured width when loading to prevent size changes
6243
+ // Use the actual measured width instead of character-based estimates
6221
6244
  const layoutStyles = {
6222
6245
  ...baseLayoutStyles,
6223
- ...(loading && calculatedMinWidth && !layoutProps.width && !layoutProps.w && !layoutProps.fullWidth
6224
- ? { minWidth: calculatedMinWidth }
6246
+ ...(loading && measuredWidth && !layoutProps.width && !layoutProps.w && !layoutProps.fullWidth
6247
+ ? { width: measuredWidth, minWidth: measuredWidth }
6225
6248
  : {})
6226
6249
  };
6227
6250
  const iconSpacing = getSpacing(size) / 2;
@@ -6414,7 +6437,7 @@ const Button = (allProps) => {
6414
6437
  ...(Platform.OS !== 'web' ? { transform: [{ translateY: 1 }] } : {})
6415
6438
  } : null,
6416
6439
  style,
6417
- ], onPress: handleInternalPress, onLayout: onLayout, onPressIn: handlePressIn, onPressOut: handlePressOut, onHoverIn: onHoverIn, onHoverOut: onHoverOut, onLongPress: onLongPress, disabled: isInteractionDisabled, children: [variant === 'gradient' && hasLinearGradient$4 && (jsx(OptionalLinearGradient$6, { colors: resolvedCustomColor
6440
+ ], onPress: handleInternalPress, onLayout: handleLayout, onPressIn: handlePressIn, onPressOut: handlePressOut, onHoverIn: onHoverIn, onHoverOut: onHoverOut, onLongPress: onLongPress, disabled: isInteractionDisabled, children: [variant === 'gradient' && hasLinearGradient$4 && (jsx(OptionalLinearGradient$6, { colors: resolvedCustomColor
6418
6441
  ? [resolvedCustomColor, theme.colors.primary[7]]
6419
6442
  : [theme.colors.primary[5], theme.colors.primary[7]], style: { position: 'absolute', zIndex: -1, top: 0, left: 0, right: 0, bottom: 0, borderRadius: radiusStyles.borderRadius }, start: { x: 0, y: 0 }, end: { x: 1, y: 0 } })), loading ? (jsxs(Fragment, { children: [jsx(Loader, { size: size, color: getLoaderColor(), style: !isIconButton ? { marginRight: iconSpacing } : undefined }), !isIconButton && renderButtonContent(displayContent)] })) : isIconButton ? (
6420
6443
  // Icon-only button
@@ -9732,593 +9755,6 @@ function UniversalCSS() {
9732
9755
  return null;
9733
9756
  }
9734
9757
 
9735
- /**
9736
- * Core Ability class for managing permissions
9737
- */
9738
- class AbilityCore {
9739
- constructor(rules = []) {
9740
- this.rules = [];
9741
- this.cache = new Map();
9742
- this.rules = [...rules];
9743
- }
9744
- /**
9745
- * Check if action is allowed on subject
9746
- */
9747
- can(action, subject, field) {
9748
- return this.check(action, subject, field).allowed;
9749
- }
9750
- /**
9751
- * Check if action is forbidden on subject
9752
- */
9753
- cannot(action, subject, field) {
9754
- return !this.can(action, subject, field);
9755
- }
9756
- /**
9757
- * Get detailed permission check result
9758
- */
9759
- check(action, subject, field) {
9760
- const cacheKey = this.getCacheKey(action, subject, field);
9761
- if (this.cache.has(cacheKey)) {
9762
- return this.cache.get(cacheKey);
9763
- }
9764
- const result = this.performCheck(action, subject, field);
9765
- this.cache.set(cacheKey, result);
9766
- return result;
9767
- }
9768
- /**
9769
- * Update ability rules and clear cache
9770
- */
9771
- update(rules) {
9772
- this.rules = [...rules];
9773
- this.cache.clear();
9774
- }
9775
- /**
9776
- * Get all current rules
9777
- */
9778
- getRules() {
9779
- return [...this.rules];
9780
- }
9781
- /**
9782
- * Clear all rules and cache
9783
- */
9784
- clear() {
9785
- this.rules = [];
9786
- this.cache.clear();
9787
- }
9788
- /**
9789
- * Perform the actual permission check
9790
- */
9791
- performCheck(action, subject, field) {
9792
- // Start with denied by default
9793
- let result = {
9794
- allowed: false,
9795
- reason: 'No matching permission rule found'
9796
- };
9797
- // Check rules in order (later rules can override earlier ones)
9798
- for (const rule of this.rules) {
9799
- if (this.ruleMatches(rule, action, subject, field)) {
9800
- result = {
9801
- allowed: !rule.inverted,
9802
- reason: rule.reason || (rule.inverted ? 'Access denied by rule' : 'Access granted by rule'),
9803
- rule
9804
- };
9805
- }
9806
- }
9807
- return result;
9808
- }
9809
- /**
9810
- * Check if a rule matches the current permission check
9811
- */
9812
- ruleMatches(rule, action, subject, field) {
9813
- // Check action match
9814
- const actions = Array.isArray(rule.action) ? rule.action : [rule.action];
9815
- if (!actions.includes(action) && !actions.includes('*')) {
9816
- return false;
9817
- }
9818
- // Check subject match
9819
- const subjects = Array.isArray(rule.subject) ? rule.subject : [rule.subject];
9820
- if (!this.subjectMatches(subjects, subject)) {
9821
- return false;
9822
- }
9823
- // Check field match (if specified)
9824
- if (field && rule.fields && !rule.fields.includes(field)) {
9825
- return false;
9826
- }
9827
- // Check conditions (if subject is an object)
9828
- if (rule.conditions && typeof subject === 'object' && subject !== null) {
9829
- return this.conditionsMatch(rule.conditions, subject);
9830
- }
9831
- return true;
9832
- }
9833
- /**
9834
- * Check if subject matches any of the rule subjects
9835
- */
9836
- subjectMatches(ruleSubjects, checkSubject) {
9837
- for (const ruleSubject of ruleSubjects) {
9838
- if (ruleSubject === '*')
9839
- return true;
9840
- if (ruleSubject === checkSubject)
9841
- return true;
9842
- // Handle class/constructor matching
9843
- if (typeof ruleSubject === 'function' && typeof checkSubject === 'object') {
9844
- if (checkSubject instanceof ruleSubject)
9845
- return true;
9846
- if (checkSubject.constructor === ruleSubject)
9847
- return true;
9848
- }
9849
- // Handle string matching for object types
9850
- if (typeof ruleSubject === 'string' && typeof checkSubject === 'object') {
9851
- if (checkSubject.__type === ruleSubject)
9852
- return true;
9853
- if (checkSubject.type === ruleSubject)
9854
- return true;
9855
- if (checkSubject.constructor.name === ruleSubject)
9856
- return true;
9857
- }
9858
- }
9859
- return false;
9860
- }
9861
- /**
9862
- * Check if conditions match the subject object
9863
- */
9864
- conditionsMatch(conditions, subject) {
9865
- for (const [key, expectedValue] of Object.entries(conditions)) {
9866
- const actualValue = subject[key];
9867
- if (!this.valuesMatch(actualValue, expectedValue)) {
9868
- return false;
9869
- }
9870
- }
9871
- return true;
9872
- }
9873
- /**
9874
- * Check if two values match (handles various comparison types)
9875
- */
9876
- valuesMatch(actual, expected) {
9877
- // Exact match
9878
- if (actual === expected)
9879
- return true;
9880
- // Array contains check
9881
- if (Array.isArray(expected) && expected.includes(actual))
9882
- return true;
9883
- if (Array.isArray(actual) && actual.includes(expected))
9884
- return true;
9885
- // Function/predicate check
9886
- if (typeof expected === 'function') {
9887
- return expected(actual);
9888
- }
9889
- // Regex match for strings
9890
- if (expected instanceof RegExp && typeof actual === 'string') {
9891
- return expected.test(actual);
9892
- }
9893
- // Object comparison (shallow)
9894
- if (typeof expected === 'object' && typeof actual === 'object' && expected !== null && actual !== null) {
9895
- return Object.keys(expected).every(key => this.valuesMatch(actual[key], expected[key]));
9896
- }
9897
- return false;
9898
- }
9899
- /**
9900
- * Generate cache key for permission check
9901
- */
9902
- getCacheKey(action, subject, field) {
9903
- const subjectKey = typeof subject === 'object'
9904
- ? JSON.stringify(subject)
9905
- : String(subject);
9906
- return `${action}:${subjectKey}:${field || ''}`;
9907
- }
9908
- }
9909
- /**
9910
- * Create a new Ability instance
9911
- */
9912
- function createAbility(rules = []) {
9913
- return new AbilityCore(rules);
9914
- }
9915
-
9916
- /**
9917
- * Permission Context
9918
- */
9919
- const PermissionContext = createContext(null);
9920
- /**
9921
- * Permission Provider Component
9922
- */
9923
- const PermissionProvider = ({ rules = [], user, children, dev = {} }) => {
9924
- const [ability] = useState(() => createAbility(rules));
9925
- const [currentUser, setCurrentUser] = useState(user);
9926
- const updateAbility = useCallback((newRules) => {
9927
- ability.update(newRules);
9928
- }, [ability]);
9929
- const can = useCallback((action, subject, field) => {
9930
- const result = ability.can(action, subject, field);
9931
- if (dev.logChecks) {
9932
- console.log(`[Permissions] Can ${action} ${subject}${field ? `.${field}` : ''}:`, result);
9933
- }
9934
- return result;
9935
- }, [ability, dev.logChecks]);
9936
- const cannot = useCallback((action, subject, field) => {
9937
- const result = ability.cannot(action, subject, field);
9938
- if (dev.logChecks) {
9939
- console.log(`[Permissions] Cannot ${action} ${subject}${field ? `.${field}` : ''}:`, result);
9940
- }
9941
- return result;
9942
- }, [ability, dev.logChecks]);
9943
- const setUser = useCallback((newUser) => {
9944
- setCurrentUser(newUser);
9945
- }, []);
9946
- const contextValue = useMemo(() => ({
9947
- ability,
9948
- updateAbility,
9949
- can,
9950
- cannot,
9951
- user: currentUser,
9952
- setUser
9953
- }), [ability, updateAbility, can, cannot, currentUser, setUser]);
9954
- return React__default.createElement(PermissionContext.Provider, { value: contextValue }, children);
9955
- };
9956
- /**
9957
- * Hook to access permission context
9958
- */
9959
- const usePermissions = (options = {}) => {
9960
- const context = useContext(PermissionContext);
9961
- if (!context) {
9962
- if (options.required) {
9963
- throw new Error('usePermissions must be used within a PermissionProvider');
9964
- }
9965
- if (options.debug) {
9966
- console.warn('[Permissions] usePermissions called outside of PermissionProvider');
9967
- }
9968
- // Return a fallback context that allows everything
9969
- return {
9970
- ability: createAbility([{ action: '*', subject: '*' }]),
9971
- updateAbility: () => { },
9972
- can: () => true,
9973
- cannot: () => false,
9974
- user: null,
9975
- setUser: () => { }
9976
- };
9977
- }
9978
- return context;
9979
- };
9980
- /**
9981
- * Hook to get the current ability instance
9982
- */
9983
- const useAbility = () => {
9984
- const { ability } = usePermissions();
9985
- return ability;
9986
- };
9987
-
9988
- /**
9989
- * Can Component - Renders children if permission is granted
9990
- */
9991
- const Can = ({ I: action, a: subject, field, children, fallback = null, ability: customAbility, style, testID, passthrough = false }) => {
9992
- const { ability: contextAbility } = usePermissions();
9993
- const ability = customAbility || contextAbility;
9994
- // Development passthrough
9995
- if (passthrough && __DEV__) {
9996
- return React__default.createElement(View, { style, testID }, children);
9997
- }
9998
- const hasPermission = subject
9999
- ? ability.can(action, subject, field)
10000
- : ability.can(action, '*', field);
10001
- if (hasPermission) {
10002
- return React__default.createElement(View, { style, testID }, children);
10003
- }
10004
- return React__default.createElement(View, { style, testID }, fallback);
10005
- };
10006
- /**
10007
- * Can Component with conditions - For object-level permissions
10008
- */
10009
- const CanWithConditions = ({ I: action, this: subject, field, children, fallback = null, ability: customAbility, style, testID, passthrough = false }) => {
10010
- const { ability: contextAbility } = usePermissions();
10011
- const ability = customAbility || contextAbility;
10012
- // Development passthrough
10013
- if (passthrough && __DEV__) {
10014
- return React__default.createElement(View, { style, testID }, children);
10015
- }
10016
- const hasPermission = ability.can(action, subject, field);
10017
- if (hasPermission) {
10018
- return React__default.createElement(View, { style, testID }, children);
10019
- }
10020
- return React__default.createElement(View, { style, testID }, fallback);
10021
- };
10022
- /**
10023
- * Cannot Component - Renders children if permission is NOT granted
10024
- */
10025
- const Cannot = ({ I: action, a: subject, field, children, fallback = null, ability: customAbility, style, testID, passthrough = false }) => {
10026
- const { ability: contextAbility } = usePermissions();
10027
- const ability = customAbility || contextAbility;
10028
- // Development passthrough
10029
- if (passthrough && __DEV__) {
10030
- return React__default.createElement(View, { style, testID }, fallback);
10031
- }
10032
- const hasPermission = subject
10033
- ? ability.can(action, subject, field)
10034
- : ability.can(action, '*', field);
10035
- if (!hasPermission) {
10036
- return React__default.createElement(View, { style, testID }, children);
10037
- }
10038
- return React__default.createElement(View, { style, testID }, fallback);
10039
- };
10040
- /**
10041
- * Permission Gate - Requires ALL permissions to pass
10042
- */
10043
- const PermissionGate = ({ permissions, children, fallback = null, ability: customAbility, onUnauthorized }) => {
10044
- const { ability: contextAbility } = usePermissions();
10045
- const ability = customAbility || contextAbility;
10046
- const allPermissionsGranted = permissions.every(({ action, subject, field }) => ability.can(action, subject, field));
10047
- React__default.useEffect(() => {
10048
- if (!allPermissionsGranted && onUnauthorized) {
10049
- onUnauthorized();
10050
- }
10051
- }, [allPermissionsGranted, onUnauthorized]);
10052
- if (allPermissionsGranted) {
10053
- return React__default.createElement(React__default.Fragment, null, children);
10054
- }
10055
- return React__default.createElement(React__default.Fragment, null, fallback);
10056
- };
10057
- /**
10058
- * Higher-Order Component for permission checking
10059
- */
10060
- function withCan(action, subject, field) {
10061
- return function CanHOC(Component) {
10062
- const WrappedComponent = (props) => {
10063
- const { fallback, ...componentProps } = props;
10064
- return React__default.createElement(Can, { I: action, a: subject, field, fallback }, React__default.createElement(Component, componentProps));
10065
- };
10066
- WrappedComponent.displayName = `withCan(${Component.displayName || Component.name})`;
10067
- return WrappedComponent;
10068
- };
10069
- }
10070
- /**
10071
- * Higher-Order Component for permission denial checking
10072
- */
10073
- function withCannot(action, subject, field) {
10074
- return function CannotHOC(Component) {
10075
- const WrappedComponent = (props) => {
10076
- const { fallback, ...componentProps } = props;
10077
- return React__default.createElement(Cannot, { I: action, a: subject, field, fallback }, React__default.createElement(Component, componentProps));
10078
- };
10079
- WrappedComponent.displayName = `withCannot(${Component.displayName || Component.name})`;
10080
- return WrappedComponent;
10081
- };
10082
- }
10083
-
10084
- /**
10085
- * Fluent API for building permissions
10086
- */
10087
- class PermissionBuilder {
10088
- constructor() {
10089
- this.rules = [];
10090
- }
10091
- /**
10092
- * Grant permission
10093
- */
10094
- allow(action, subject, field) {
10095
- this.rules.push({
10096
- action,
10097
- subject: subject || '*',
10098
- fields: field ? [field] : undefined,
10099
- inverted: false
10100
- });
10101
- return this;
10102
- }
10103
- /**
10104
- * Deny permission
10105
- */
10106
- forbid(action, subject, field) {
10107
- this.rules.push({
10108
- action,
10109
- subject: subject || '*',
10110
- fields: field ? [field] : undefined,
10111
- inverted: true
10112
- });
10113
- return this;
10114
- }
10115
- /**
10116
- * Conditional permission
10117
- */
10118
- allowIf(action, subject, conditions, field) {
10119
- this.rules.push({
10120
- action,
10121
- subject: subject || '*',
10122
- fields: field ? [field] : undefined,
10123
- inverted: false,
10124
- conditions
10125
- });
10126
- return this;
10127
- }
10128
- /**
10129
- * Conditional denial
10130
- */
10131
- forbidIf(action, subject, conditions, field) {
10132
- this.rules.push({
10133
- action,
10134
- subject: subject || '*',
10135
- fields: field ? [field] : undefined,
10136
- inverted: true,
10137
- conditions
10138
- });
10139
- return this;
10140
- }
10141
- /**
10142
- * Grant all actions on a subject
10143
- */
10144
- manage(subject) {
10145
- this.rules.push({
10146
- action: '*',
10147
- subject,
10148
- inverted: false
10149
- });
10150
- return this;
10151
- }
10152
- /**
10153
- * Forbid all actions on a subject
10154
- */
10155
- forbidAll(subject) {
10156
- this.rules.push({
10157
- action: '*',
10158
- subject,
10159
- inverted: true
10160
- });
10161
- return this;
10162
- }
10163
- /**
10164
- * Role-based permissions
10165
- */
10166
- role(roleName, callback) {
10167
- const roleBuilder = new RoleBuilder(roleName);
10168
- callback(roleBuilder);
10169
- this.rules.push(...roleBuilder.getRules());
10170
- return this;
10171
- }
10172
- /**
10173
- * Build the ability with all rules
10174
- */
10175
- build() {
10176
- return new AbilityCore(this.rules);
10177
- }
10178
- /**
10179
- * Get all rules
10180
- */
10181
- getRules() {
10182
- return [...this.rules];
10183
- }
10184
- /**
10185
- * Clear all rules
10186
- */
10187
- clear() {
10188
- this.rules = [];
10189
- return this;
10190
- }
10191
- /**
10192
- * Merge rules from another builder
10193
- */
10194
- merge(other) {
10195
- this.rules.push(...other.getRules());
10196
- return this;
10197
- }
10198
- }
10199
- /**
10200
- * Role-specific permission builder
10201
- */
10202
- class RoleBuilder {
10203
- constructor(roleName) {
10204
- this.roleName = roleName;
10205
- this.rules = [];
10206
- }
10207
- /**
10208
- * Grant permission for this role
10209
- */
10210
- can(action, subject, field) {
10211
- this.rules.push({
10212
- action,
10213
- subject: subject || '*',
10214
- fields: field ? [field] : undefined,
10215
- inverted: false,
10216
- conditions: { role: this.roleName }
10217
- });
10218
- return this;
10219
- }
10220
- /**
10221
- * Deny permission for this role
10222
- */
10223
- cannot(action, subject, field) {
10224
- this.rules.push({
10225
- action,
10226
- subject: subject || '*',
10227
- fields: field ? [field] : undefined,
10228
- inverted: true,
10229
- conditions: { role: this.roleName }
10230
- });
10231
- return this;
10232
- }
10233
- /**
10234
- * Manage all actions on subject for this role
10235
- */
10236
- manage(subject) {
10237
- this.rules.push({
10238
- action: '*',
10239
- subject,
10240
- inverted: false,
10241
- conditions: { role: this.roleName }
10242
- });
10243
- return this;
10244
- }
10245
- /**
10246
- * Get all rules for this role
10247
- */
10248
- getRules() {
10249
- return [...this.rules];
10250
- }
10251
- }
10252
- /**
10253
- * Common permission patterns
10254
- */
10255
- class PermissionPatterns {
10256
- /**
10257
- * Admin permissions - can do everything
10258
- */
10259
- static admin() {
10260
- return new PermissionBuilder()
10261
- .manage('*');
10262
- }
10263
- /**
10264
- * User permissions - basic CRUD on own resources
10265
- */
10266
- static user(userId) {
10267
- return new PermissionBuilder()
10268
- .allowIf('read', 'User', { id: userId })
10269
- .allowIf('update', 'User', { id: userId })
10270
- .allowIf('delete', 'User', { id: userId })
10271
- .allow('read', 'public');
10272
- }
10273
- /**
10274
- * Guest permissions - read-only public content
10275
- */
10276
- static guest() {
10277
- return new PermissionBuilder()
10278
- .allow('read', 'public');
10279
- }
10280
- /**
10281
- * Moderator permissions - manage content but not users
10282
- */
10283
- static moderator() {
10284
- return new PermissionBuilder()
10285
- .manage('Content')
10286
- .manage('Comment')
10287
- .allow('read', 'User')
10288
- .forbid('delete', 'User');
10289
- }
10290
- /**
10291
- * Owner permissions - full control over owned resources
10292
- */
10293
- static owner(ownerId) {
10294
- return new PermissionBuilder()
10295
- .allowIf('*', '*', { ownerId })
10296
- .allow('create', '*');
10297
- }
10298
- }
10299
- /**
10300
- * Helper function to create a new permission builder
10301
- */
10302
- function permissions() {
10303
- return new PermissionBuilder();
10304
- }
10305
- /**
10306
- * Helper function to create ability from rules array
10307
- */
10308
- function defineAbility(callback) {
10309
- const builder = new PermissionBuilder();
10310
- callback(builder);
10311
- return builder.build();
10312
- }
10313
- /**
10314
- * Helper function to create common role-based abilities
10315
- */
10316
- function defineRoleAbility(role, callback) {
10317
- const roleBuilder = new RoleBuilder(role);
10318
- callback(roleBuilder);
10319
- return new AbilityCore(roleBuilder.getRules());
10320
- }
10321
-
10322
9758
  const ThemeModeContext = createContext(null);
10323
9759
  // Default persistence using localStorage (web only)
10324
9760
  const defaultPersistence = {
@@ -10448,7 +9884,6 @@ const OverlayBoundary = React__default.memo(function OverlayBoundary({ enabled,
10448
9884
  const I18nBoundary = React__default.memo(function I18nBoundary({ locale, fallbackLocale, resources, children }) {
10449
9885
  return (jsx(I18nProvider, { initial: { locale, fallbackLocale, resources }, children: children }));
10450
9886
  });
10451
- const DEFAULT_PERMISSION_RULES = [{ action: '*', subject: '*' }];
10452
9887
  /**
10453
9888
  * Internal component that uses the enhanced theme mode when config is provided
10454
9889
  */
@@ -10486,30 +9921,20 @@ function PlatformBlocksContent({ children, theme, inherit = true, withCSSVariabl
10486
9921
  document.documentElement.setAttribute('data-platform-blocks-color-scheme', target);
10487
9922
  }
10488
9923
  }, [effectiveColorScheme, osColorScheme]);
10489
- const mainContent = (jsxs(ThemeBoundary, { theme: resolvedTheme, inherit: inherit, withCSSVariables: withCSSVariables, cssVariablesSelector: cssVariablesSelector, withGlobalCSS: withGlobalCSS, children: [children, withSpotlight && jsx(SpotlightController, { config: spotlightConfig })] }));
10490
- return (jsx(OverlayBoundary, { enabled: withOverlays, children: mainContent }));
9924
+ const mainContent = (jsx(ThemeBoundary, { theme: resolvedTheme, inherit: inherit, withCSSVariables: withCSSVariables, cssVariablesSelector: cssVariablesSelector, withGlobalCSS: withGlobalCSS, children: jsxs(OverlayBoundary, { enabled: withOverlays, children: [children, withSpotlight && jsx(SpotlightController, { config: spotlightConfig })] }) }));
9925
+ return mainContent;
10491
9926
  }
10492
9927
  /**
10493
9928
  * Main provider component for Platform Blocks library
10494
9929
  * Provides theme context and injects CSS variables
10495
9930
  */
10496
- function PlatformBlocksProvider({ children, theme, inherit = true, withCSSVariables = true, cssVariablesSelector = ':root', colorSchemeMode = 'auto', withOverlays = true, withSpotlight = false, withGlobalCSS = true, themeModeConfig, spotlightConfig, locale = 'en', fallbackLocale = 'en', i18nResources, direction, haptics, permissions }) {
9931
+ function PlatformBlocksProvider({ children, theme, inherit = true, withCSSVariables = true, cssVariablesSelector = ':root', colorSchemeMode = 'auto', withOverlays = true, withSpotlight = false, withGlobalCSS = true, themeModeConfig, spotlightConfig, locale = 'en', fallbackLocale = 'en', i18nResources, direction, haptics }) {
10497
9932
  const i18nStore = useMemo(() => i18nResources || { en: { translation: {} } }, [i18nResources]);
10498
9933
  const content = (jsx(PlatformBlocksContent, { theme: theme, inherit: inherit, withCSSVariables: withCSSVariables, cssVariablesSelector: cssVariablesSelector, colorSchemeMode: colorSchemeMode, withOverlays: withOverlays, withSpotlight: withSpotlight, withGlobalCSS: withGlobalCSS, spotlightConfig: spotlightConfig, themeModeConfig: themeModeConfig, children: children }));
10499
9934
  const themedTree = themeModeConfig ? (jsx(ThemeModeProvider, { config: themeModeConfig, children: content })) : (content);
10500
9935
  const directionConfig = direction === false ? null : (direction !== null && direction !== void 0 ? direction : {});
10501
9936
  const hapticsConfig = haptics === false ? null : (haptics !== null && haptics !== void 0 ? haptics : {});
10502
- const permissionConfig = permissions === false ? null : (() => {
10503
- const base = { ...(permissions !== null && permissions !== void 0 ? permissions : {}) };
10504
- if (base.rules === undefined) {
10505
- base.rules = DEFAULT_PERMISSION_RULES;
10506
- }
10507
- return base;
10508
- })();
10509
9937
  let enhancedTree = themedTree;
10510
- if (permissionConfig) {
10511
- enhancedTree = (jsx(PermissionProvider, { ...permissionConfig, children: enhancedTree }));
10512
- }
10513
9938
  if (hapticsConfig) {
10514
9939
  enhancedTree = (jsx(HapticsProvider, { ...hapticsConfig, children: enhancedTree }));
10515
9940
  }
@@ -10999,14 +10424,18 @@ function usePopoverPositioning(isOpen, options = {}) {
10999
10424
  // Get popover dimensions
11000
10425
  // Always use measureElement for robustness across platforms (RNW refs may not expose getBoundingClientRect)
11001
10426
  let popoverDimensions = { width: 200, height: 100 }; // sensible defaults for initial calculation
10427
+ let hasMeasuredPopover = false;
11002
10428
  if (popoverRef.current) {
11003
10429
  const popoverRect = await measureElement(popoverRef);
11004
10430
  if (popoverRect.width > 0 && popoverRect.height > 0) {
11005
10431
  popoverDimensions = { width: popoverRect.width, height: popoverRect.height };
10432
+ hasMeasuredPopover = true;
11006
10433
  }
11007
10434
  }
11008
10435
  // Calculate optimal position
11009
10436
  const result = calculateOverlayPositionEnhanced(anchorRect, popoverDimensions, positioningOptionsRef.current);
10437
+ // Mark whether this position is based on actual measurements
10438
+ result._hasMeasuredPopover = hasMeasuredPopover;
11010
10439
  setPosition(result);
11011
10440
  }
11012
10441
  catch (error) {
@@ -17257,6 +16686,7 @@ const TextInputBase = factory((props, ref) => {
17257
16686
  const { value, onChangeText, onEnter, label, description, error, helperText, disabled, required, size = 'md', withAsterisk, placeholder, startSection, endSection, focused: focusedProp, accessibilityLabel, accessibilityHint, testID, textInputProps, style, radius, secureTextEntry, clearable, clearButtonLabel, onClear, inputRef, name, keyboardFocusId, ...rest } = otherProps;
17258
16687
  const renderDisclaimer = useDisclaimer(disclaimerData.disclaimer, disclaimerData.disclaimerProps);
17259
16688
  const [focused, setFocused] = useState(false);
16689
+ const [cursorVisible, setCursorVisible] = useState(true);
17260
16690
  const theme = useTheme();
17261
16691
  const { isRTL } = useDirection();
17262
16692
  const internalInputRef = useRef(null);
@@ -17328,6 +16758,16 @@ const TextInputBase = factory((props, ref) => {
17328
16758
  return '';
17329
16759
  return '•'.repeat(length);
17330
16760
  }, [isSecureEntry, normalizedValue]);
16761
+ // Blinking cursor for secure entry (simple interval toggle)
16762
+ useEffect(() => {
16763
+ if (focused && isSecureEntry) {
16764
+ setCursorVisible(true);
16765
+ const interval = setInterval(() => {
16766
+ setCursorVisible(v => !v);
16767
+ }, 530);
16768
+ return () => clearInterval(interval);
16769
+ }
16770
+ }, [focused, isSecureEntry]);
17331
16771
  const textColor = (_a = flattenedInputStyle === null || flattenedInputStyle === void 0 ? void 0 : flattenedInputStyle.color) !== null && _a !== void 0 ? _a : (styleProps.disabled ? theme.text.disabled : theme.text.primary);
17332
16772
  const resolvedInputStyle = useMemo(() => {
17333
16773
  const base = [styles.input];
@@ -17429,7 +16869,12 @@ const TextInputBase = factory((props, ref) => {
17429
16869
  }
17430
16870
  }, [keyboardManager, pendingFocusTarget, focusTargetId]);
17431
16871
  const disclaimerNode = renderDisclaimer();
17432
- return (jsxs(View, { style: [styles.container, spacingStyles, layoutStyles, style], ...rest, children: [jsx(FieldHeader, { label: label, description: description, required: required, withAsterisk: withAsterisk, disabled: disabled, error: !!error, size: size }), jsxs(View, { style: styles.inputContainer, children: [startSection && (jsx(View, { style: styles.startSection, children: startSection })), jsxs(View, { style: { flex: 1, position: 'relative', justifyContent: 'center' }, children: [jsx(TextInput, { ref: assignInputRef, value: value, onChangeText: onChangeText, onFocus: handleFocus, onBlur: handleBlur, onSubmitEditing: handleSubmitEditing, editable: !disabled, placeholder: placeholder, placeholderTextColor: theme.text.muted, style: resolvedInputStyle, selectionColor: selectionColorProp !== null && selectionColorProp !== void 0 ? selectionColorProp : textColor, testID: testID, secureTextEntry: isSecureEntry, ...inputAccessibilityProps, ...restTextInputProps }), isSecureEntry && maskedValue.length > 0 && (jsx(Text$1, { pointerEvents: "none", accessible: false, style: overlayStyle, numberOfLines: 1, ellipsizeMode: "clip", children: maskedValue }))] }), (showClearButton || endSection) && (jsxs(View, { style: styles.endSection, children: [showClearButton && (jsx(ClearButton, { onPress: handleClear, size: size, accessibilityLabel: clearButtonLabelText, hasRightSection: !!endSection })), endSection] }))] }), disclaimerNode, error && (jsx(Text$1, { style: styles.error, role: "alert", accessibilityLiveRegion: "polite", children: error })), helperText && !error && (jsx(Text$1, { style: styles.helperText, children: helperText }))] }));
16872
+ return (jsxs(View, { style: [styles.container, spacingStyles, layoutStyles, style], ...rest, children: [jsx(FieldHeader, { label: label, description: description, required: required, withAsterisk: withAsterisk, disabled: disabled, error: !!error, size: size }), jsxs(View, { style: styles.inputContainer, children: [startSection && (jsx(View, { style: styles.startSection, children: startSection })), jsxs(View, { style: { flex: 1, position: 'relative', justifyContent: 'center' }, children: [jsx(TextInput, { ref: assignInputRef, value: value, onChangeText: onChangeText, onFocus: handleFocus, onBlur: handleBlur, onSubmitEditing: handleSubmitEditing, editable: !disabled, placeholder: placeholder, placeholderTextColor: theme.text.muted, style: resolvedInputStyle, selectionColor: selectionColorProp !== null && selectionColorProp !== void 0 ? selectionColorProp : textColor, testID: testID, secureTextEntry: isSecureEntry, ...inputAccessibilityProps, ...restTextInputProps }), isSecureEntry && (jsxs(View, { pointerEvents: "none", style: [overlayStyle, { flexDirection: 'row', alignItems: 'center' }], children: [jsx(Text$1, { accessible: false, style: { color: textColor, fontSize: styles.input.fontSize, fontFamily: theme.fontFamily }, numberOfLines: 1, children: maskedValue }), focused && cursorVisible && (jsx(View, { style: {
16873
+ width: 1,
16874
+ height: styles.input.fontSize || 16,
16875
+ backgroundColor: textColor,
16876
+ marginLeft: 1,
16877
+ } }))] }))] }), (showClearButton || endSection) && (jsxs(View, { style: styles.endSection, children: [showClearButton && (jsx(ClearButton, { onPress: handleClear, size: size, accessibilityLabel: clearButtonLabelText, hasRightSection: !!endSection })), endSection] }))] }), disclaimerNode, error && (jsx(Text$1, { style: styles.error, role: "alert", accessibilityLiveRegion: "polite", children: error })), helperText && !error && (jsx(Text$1, { style: styles.helperText, children: helperText }))] }));
17433
16878
  });
17434
16879
 
17435
16880
  const getInputTypeConfig = (type) => {
@@ -17987,18 +17432,19 @@ const useTextAreaStyles = (props) => {
17987
17432
  ? theme.colors.error[5]
17988
17433
  : styleProps.focused
17989
17434
  ? theme.colors.primary[5]
17990
- : styleProps.disabled
17991
- ? theme.backgrounds.border
17992
- : 'transparent',
17435
+ : theme.backgrounds.border,
17993
17436
  borderRadius: DESIGN_TOKENS.radius.lg,
17994
- borderWidth: DESIGN_TOKENS.radius.xs,
17437
+ borderWidth: 2,
17995
17438
  paddingHorizontal: DESIGN_TOKENS.spacing.sm,
17996
17439
  paddingVertical: DESIGN_TOKENS.spacing.xs,
17440
+ // Match Input focus treatment on web
17997
17441
  ...(styleProps.focused && !styleProps.disabled && Platform.OS === 'web' && {
17998
17442
  boxShadow: `0 0 0 2px ${((_a = theme.states) === null || _a === void 0 ? void 0 : _a.focusRing) || theme.colors.primary[2]}`,
17999
17443
  }),
17444
+ // Light elevation similar to Input
18000
17445
  ...(!styleProps.disabled && theme.colorScheme === 'light' && {
18001
17446
  elevation: 1,
17447
+ boxShadow: '0 1px 2px rgba(0, 0, 0, 0.05)',
18002
17448
  }),
18003
17449
  opacity: styleProps.disabled ? DESIGN_TOKENS.opacity.disabled : 1,
18004
17450
  },
@@ -19680,7 +19126,7 @@ const useSwitchStyles = (props) => {
19680
19126
  ...(Platform.OS === 'web' && { userSelect: 'none' }),
19681
19127
  },
19682
19128
  labelContainer: {
19683
- flex: 1,
19129
+ flexShrink: 1,
19684
19130
  justifyContent: 'center',
19685
19131
  },
19686
19132
  labelDisabled: {
@@ -19849,8 +19295,8 @@ const Switch = factory((rawProps, ref) => {
19849
19295
  const LayoutComponent = isVertical ? Column : Row;
19850
19296
  // For vertical layouts (top/bottom), we want tighter spacing and center alignment
19851
19297
  const layoutProps = isVertical
19852
- ? { gap: 'xs', style: { alignItems: 'center' } }
19853
- : { gap: 'sm', style: { alignItems: 'center' } };
19298
+ ? { gap: 'xs', align: 'center' }
19299
+ : { gap: 'sm', align: 'center' };
19854
19300
  const disclaimerNode = renderDisclaimer();
19855
19301
  return (jsxs(View, { style: spacingStyles, children: [jsxs(LayoutComponent, { ...layoutProps, children: [labelPosition === 'top' && labelElement, labelPosition === 'left' && labelElement, switchElement, labelPosition === 'right' && labelElement, labelPosition === 'bottom' && labelElement] }), disclaimerNode ? (jsx(View, { style: { width: '100%' }, children: disclaimerNode })) : null] }));
19856
19302
  });
@@ -27464,7 +26910,7 @@ const MiniCalendar = ({ value, onChange, defaultValue, numberOfDays = 7, default
27464
26910
  }
27465
26911
  onChange === null || onChange === void 0 ? void 0 : onChange(date);
27466
26912
  }, [isControlled, maxDate, minDate, onChange]);
27467
- return (jsxs(View, { children: [jsxs(Flex, { direction: "row", justify: "space-between", align: "center", style: { marginBottom: 16 }, children: [jsx(Pressable, { onPress: handlePrevious, style: ({ pressed }) => [
26913
+ return (jsxs(View, { style: { alignSelf: 'flex-start' }, children: [jsxs(Flex, { direction: "row", justify: "space-between", align: "center", style: { marginBottom: 16 }, children: [jsx(Pressable, { onPress: handlePrevious, style: ({ pressed }) => [
27468
26914
  {
27469
26915
  padding: 8,
27470
26916
  borderRadius: 6,
@@ -27476,7 +26922,7 @@ const MiniCalendar = ({ value, onChange, defaultValue, numberOfDays = 7, default
27476
26922
  borderRadius: 6,
27477
26923
  backgroundColor: pressed ? theme.colors.gray[2] : 'transparent',
27478
26924
  },
27479
- ], ...nextControlProps, children: jsx(Icon, { name: "chevron-right", size: 16, color: theme.colors.gray[6] }) })] }), jsx(ScrollView, { horizontal: true, showsHorizontalScrollIndicator: false, children: jsx(Flex, { direction: "row", gap: 4, children: days.map((date) => {
26925
+ ], ...nextControlProps, children: jsx(Icon, { name: "chevron-right", size: 16, color: theme.colors.gray[6] }) })] }), jsx(ScrollView, { horizontal: true, showsHorizontalScrollIndicator: false, contentContainerStyle: { flexGrow: 0 }, style: { flexGrow: 0 }, children: jsx(Flex, { direction: "row", gap: 4, children: days.map((date) => {
27480
26926
  const isSelected = selectedDate ? dateUtils$1.isSameDay(date, selectedDate) : false;
27481
26927
  const isToday = dateUtils$1.isToday(date);
27482
26928
  const isWeekend = dateUtils$1.isWeekend(date);
@@ -30217,6 +29663,7 @@ function MenuBase(props, ref) {
30217
29663
  const overlayId = openOverlay({
30218
29664
  content: menuDropdown,
30219
29665
  anchor: { x: positionResult.x, y: positionResult.y, width: overlaySize.width, height: overlaySize.height },
29666
+ placement: positionResult.placement || position,
30220
29667
  closeOnClickOutside,
30221
29668
  closeOnEscape,
30222
29669
  strategy,
@@ -32186,7 +31633,7 @@ function usePopoverContext(component) {
32186
31633
  const DEFAULT_ARROW_SIZE = 7;
32187
31634
  const PopoverBase = (props, ref) => {
32188
31635
  var _a;
32189
- const { children, opened: controlledOpened, defaultOpened = false, onChange, onOpen, onClose, onDismiss, disabled = false, closeOnClickOutside = true, closeOnEscape = true, clickOutsideEvents, // currently not implemented
31636
+ const { children, opened: controlledOpened, defaultOpened = false, onChange, onOpen, onClose, onDismiss, trigger = 'click', disabled = false, closeOnClickOutside = true, closeOnEscape = true, clickOutsideEvents, // currently not implemented
32190
31637
  trapFocus = false, keepMounted = false, returnFocus = false, withinPortal = true, withOverlay = false, overlayProps, width, minWidth, minHeight, maxWidth, maxHeight, radius, shadow, zIndex = 300, position = 'bottom', offset = 8, floatingStrategy = 'fixed', middlewares, preventPositionChangeWhenVisible = false, hideDetached = true, viewport, keyboardAvoidance = true, fallbackPlacements, boundary, withRoles = true, id, withArrow = false, arrowSize = DEFAULT_ARROW_SIZE, arrowRadius = 0, arrowOffset = 5, arrowPosition = 'center', onPositionChange, testID, ...rest } = props;
32191
31638
  const theme = useTheme();
32192
31639
  const { spacingProps } = extractSpacingProps(rest);
@@ -32198,9 +31645,18 @@ const PopoverBase = (props, ref) => {
32198
31645
  const openedRef = useRef(opened);
32199
31646
  const closingReasonRef = useRef(null);
32200
31647
  const anchorMeasurementsRef = useRef(null);
31648
+ const hoverTimeoutRef = useRef(null);
32201
31649
  useEffect(() => {
32202
31650
  openedRef.current = opened;
32203
31651
  }, [opened]);
31652
+ // Cleanup hover timeout on unmount
31653
+ useEffect(() => {
31654
+ return () => {
31655
+ if (hoverTimeoutRef.current) {
31656
+ clearTimeout(hoverTimeoutRef.current);
31657
+ }
31658
+ };
31659
+ }, []);
32204
31660
  const resolvedOffset = typeof offset === 'number' ? offset : (_a = offset === null || offset === void 0 ? void 0 : offset.mainAxis) !== null && _a !== void 0 ? _a : 8;
32205
31661
  const resolvedFlip = preventPositionChangeWhenVisible
32206
31662
  ? false
@@ -32213,7 +31669,7 @@ const PopoverBase = (props, ref) => {
32213
31669
  ? false
32214
31670
  : true;
32215
31671
  const resolvedStrategy = floatingStrategy !== null && floatingStrategy !== void 0 ? floatingStrategy : 'fixed';
32216
- const { position: positioningResult, anchorRef, popoverRef, showOverlay, hideOverlay, updatePosition } = useDropdownPositioning({
31672
+ const { position: positioningResult, anchorRef, popoverRef, showOverlay, hideOverlay, updatePosition, isPositioning } = useDropdownPositioning({
32217
31673
  isOpen: opened && !disabled && !!dropdownState,
32218
31674
  placement: position,
32219
31675
  offset: resolvedOffset,
@@ -32225,9 +31681,20 @@ const PopoverBase = (props, ref) => {
32225
31681
  fallbackPlacements,
32226
31682
  viewport,
32227
31683
  onClose: () => handleOverlayClose('dismiss'),
32228
- closeOnClickOutside,
31684
+ closeOnClickOutside: trigger === 'hover' ? false : closeOnClickOutside,
32229
31685
  closeOnEscape,
32230
31686
  });
31687
+ // Track if we've done measurement-based positioning to avoid flicker
31688
+ const hasPositionedRef = useRef(false);
31689
+ useEffect(() => {
31690
+ // Only mark as positioned when we have a measurement-based position
31691
+ if (opened && positioningResult && positioningResult._hasMeasuredPopover) {
31692
+ hasPositionedRef.current = true;
31693
+ }
31694
+ if (!opened) {
31695
+ hasPositionedRef.current = false;
31696
+ }
31697
+ }, [opened, positioningResult]);
32231
31698
  const popoverStyles = useMemo(() => createPopoverStyles(theme)({
32232
31699
  radius,
32233
31700
  shadow,
@@ -32331,6 +31798,28 @@ const PopoverBase = (props, ref) => {
32331
31798
  openPopover();
32332
31799
  }
32333
31800
  }, [closePopover, openPopover]);
31801
+ // Hover-specific handlers with delay to prevent glitching when moving between target and dropdown
31802
+ const handleHoverOpen = useCallback(() => {
31803
+ if (hoverTimeoutRef.current) {
31804
+ clearTimeout(hoverTimeoutRef.current);
31805
+ hoverTimeoutRef.current = null;
31806
+ }
31807
+ openPopover();
31808
+ }, [openPopover]);
31809
+ const handleHoverClose = useCallback(() => {
31810
+ if (hoverTimeoutRef.current) {
31811
+ clearTimeout(hoverTimeoutRef.current);
31812
+ }
31813
+ hoverTimeoutRef.current = setTimeout(() => {
31814
+ closePopover('programmatic');
31815
+ hoverTimeoutRef.current = null;
31816
+ }, 150); // Delay to allow mouse to move to dropdown
31817
+ }, [closePopover]);
31818
+ // Store hover handlers in refs to avoid causing re-renders in useEffect
31819
+ const hoverHandlersRef = useRef({ open: handleHoverOpen, close: handleHoverClose });
31820
+ useEffect(() => {
31821
+ hoverHandlersRef.current = { open: handleHoverOpen, close: handleHoverClose };
31822
+ }, [handleHoverOpen, handleHoverClose]);
32334
31823
  useEffect(() => {
32335
31824
  if (opened) {
32336
31825
  updateAnchorMeasurements();
@@ -32389,14 +31878,26 @@ const PopoverBase = (props, ref) => {
32389
31878
  if (typeof resolvedMaxHeight === 'number')
32390
31879
  sizeStyles.maxHeight = resolvedMaxHeight;
32391
31880
  const dropdownStyle = [popoverStyles.dropdown, dropdownState.style, sizeStyles];
32392
- const content = (jsxs(View, { ref: popoverRef, style: [popoverStyles.wrapper, widthOverride ? { width: widthOverride } : null], pointerEvents: dropdownState.trapFocus ? 'auto' : 'box-none', testID: dropdownState.testID, onLayout: handleDropdownLayout, ...dropdownState.containerProps, children: [jsx(View, { style: dropdownStyle, children: dropdownState.content }), withArrow && (jsx(View, { style: getArrowStyle(positioningResult.placement, arrowSize, arrowRadius, arrowOffset, arrowPosition, theme) }))] }));
31881
+ // Hover handlers for the dropdown to keep it open when mouse moves from target to dropdown
31882
+ const dropdownHoverHandlers = trigger === 'hover' && Platform.OS === 'web'
31883
+ ? {
31884
+ onMouseEnter: () => hoverHandlersRef.current.open(),
31885
+ onMouseLeave: () => hoverHandlersRef.current.close(),
31886
+ }
31887
+ : {};
31888
+ // Hide content until we have measurement-based positioning to prevent visual "snap"
31889
+ const hasMeasuredPosition = (positioningResult === null || positioningResult === void 0 ? void 0 : positioningResult._hasMeasuredPopover) === true;
31890
+ const visibilityStyle = !hasMeasuredPosition && Platform.OS === 'web'
31891
+ ? { opacity: 0 }
31892
+ : {};
31893
+ const content = (jsxs(View, { ref: popoverRef, style: [popoverStyles.wrapper, widthOverride ? { width: widthOverride } : null, visibilityStyle], pointerEvents: trigger === 'hover' ? 'auto' : (dropdownState.trapFocus ? 'auto' : 'box-none'), testID: dropdownState.testID, onLayout: handleDropdownLayout, ...dropdownHoverHandlers, ...dropdownState.containerProps, children: [jsx(View, { style: dropdownStyle, children: dropdownState.content }), withArrow && (jsx(View, { style: getArrowStyle(positioningResult.placement, arrowSize, arrowRadius, arrowOffset, arrowPosition, theme) }))] }));
32393
31894
  showOverlay(content, {
32394
31895
  width: widthOverride,
32395
31896
  maxHeight: resolvedMaxHeight,
32396
31897
  zIndex,
32397
31898
  });
32398
31899
  onPositionChange === null || onPositionChange === void 0 ? void 0 : onPositionChange(positioningResult.placement);
32399
- }, [opened, dropdownState, positioningResult, popoverRef, showOverlay, hideOverlay, popoverStyles.dropdown, popoverStyles.wrapper, width, maxHeight, minWidth, minHeight, maxWidth, withArrow, arrowSize, arrowRadius, arrowOffset, arrowPosition, theme, zIndex, onPositionChange, schedulePositionUpdate]);
31900
+ }, [opened, dropdownState, positioningResult, popoverRef, showOverlay, hideOverlay, popoverStyles.dropdown, popoverStyles.wrapper, width, maxHeight, minWidth, minHeight, maxWidth, withArrow, arrowSize, arrowRadius, arrowOffset, arrowPosition, theme, zIndex, onPositionChange, schedulePositionUpdate, trigger, isPositioning]);
32400
31901
  useEffect(() => {
32401
31902
  return () => {
32402
31903
  hideOverlay();
@@ -32417,6 +31918,8 @@ const PopoverBase = (props, ref) => {
32417
31918
  open: openPopover,
32418
31919
  close: () => closePopover('programmatic'),
32419
31920
  toggle: togglePopover,
31921
+ hoverOpen: handleHoverOpen,
31922
+ hoverClose: handleHoverClose,
32420
31923
  registerDropdown,
32421
31924
  unregisterDropdown,
32422
31925
  anchorRef,
@@ -32425,7 +31928,8 @@ const PopoverBase = (props, ref) => {
32425
31928
  withRoles,
32426
31929
  disabled,
32427
31930
  returnFocus,
32428
- }), [opened, openPopover, closePopover, togglePopover, registerDropdown, unregisterDropdown, anchorRef, targetId, dropdownId, withRoles, disabled, returnFocus]);
31931
+ trigger,
31932
+ }), [opened, openPopover, closePopover, togglePopover, handleHoverOpen, handleHoverClose, registerDropdown, unregisterDropdown, anchorRef, targetId, dropdownId, withRoles, disabled, returnFocus, trigger]);
32429
31933
  const setContainerRef = useCallback((node) => {
32430
31934
  if (typeof ref === 'function') {
32431
31935
  ref(node);
@@ -32475,16 +31979,44 @@ const PopoverTargetBase = (props, ref) => {
32475
31979
  : { id: context.targetId };
32476
31980
  const composedRef = mergeRefs(children.ref, externalTargetRef);
32477
31981
  const triggerHandlers = {};
32478
- triggerHandlers.onPress = (...args) => {
32479
- const tgt = targetProps;
32480
- if (tgt && typeof tgt.onPress === 'function') {
32481
- tgt.onPress(...args);
32482
- }
32483
- if (typeof childProps.onPress === 'function') {
32484
- childProps.onPress(...args);
32485
- }
32486
- context.toggle();
32487
- };
31982
+ const wrapperHoverHandlers = {};
31983
+ // Click trigger: toggle on press
31984
+ if (context.trigger === 'click') {
31985
+ triggerHandlers.onPress = (...args) => {
31986
+ const tgt = targetProps;
31987
+ if (tgt && typeof tgt.onPress === 'function') {
31988
+ tgt.onPress(...args);
31989
+ }
31990
+ if (typeof childProps.onPress === 'function') {
31991
+ childProps.onPress(...args);
31992
+ }
31993
+ context.toggle();
31994
+ };
31995
+ }
31996
+ // Hover trigger: open/close on mouse enter/leave (web only)
31997
+ // Applied to the wrapper View for reliable hover detection
31998
+ if (context.trigger === 'hover' && Platform.OS === 'web') {
31999
+ wrapperHoverHandlers.onMouseEnter = (...args) => {
32000
+ const tgt = targetProps;
32001
+ if (tgt && typeof tgt.onMouseEnter === 'function') {
32002
+ tgt.onMouseEnter(...args);
32003
+ }
32004
+ if (typeof childProps.onMouseEnter === 'function') {
32005
+ childProps.onMouseEnter(...args);
32006
+ }
32007
+ context.hoverOpen();
32008
+ };
32009
+ wrapperHoverHandlers.onMouseLeave = (...args) => {
32010
+ const tgt = targetProps;
32011
+ if (tgt && typeof tgt.onMouseLeave === 'function') {
32012
+ tgt.onMouseLeave(...args);
32013
+ }
32014
+ if (typeof childProps.onMouseLeave === 'function') {
32015
+ childProps.onMouseLeave(...args);
32016
+ }
32017
+ context.hoverClose();
32018
+ };
32019
+ }
32488
32020
  if (Platform.OS === 'web') {
32489
32021
  triggerHandlers.onKeyDown = (event) => {
32490
32022
  const tgt = targetProps;
@@ -32506,7 +32038,14 @@ const PopoverTargetBase = (props, ref) => {
32506
32038
  };
32507
32039
  }
32508
32040
  const dynamicRefProp = { [refProp]: composedRef };
32509
- delete sanitizedTargetProps.onPress;
32041
+ // Remove handlers that we're overriding from sanitizedTargetProps
32042
+ if (context.trigger === 'click') {
32043
+ delete sanitizedTargetProps.onPress;
32044
+ }
32045
+ if (context.trigger === 'hover') {
32046
+ delete sanitizedTargetProps.onMouseEnter;
32047
+ delete sanitizedTargetProps.onMouseLeave;
32048
+ }
32510
32049
  delete sanitizedTargetProps.onKeyDown;
32511
32050
  const mergedProps = {
32512
32051
  ...sanitizedTargetProps,
@@ -32518,7 +32057,7 @@ const PopoverTargetBase = (props, ref) => {
32518
32057
  mergedProps.disabled = true;
32519
32058
  }
32520
32059
  const anchorWrapperRef = mergeRefs(context.anchorRef, ref);
32521
- return (jsx(View, { ref: anchorWrapperRef, collapsable: false, children: cloneElement(children, mergedProps) }));
32060
+ return (jsx(View, { ref: anchorWrapperRef, collapsable: false, ...wrapperHoverHandlers, children: cloneElement(children, mergedProps) }));
32522
32061
  };
32523
32062
  const PopoverDropdownBase = (props, _ref) => {
32524
32063
  const { children, trapFocus = false, keepMounted, style, testID, ...rest } = props;
@@ -32562,8 +32101,11 @@ function getArrowStyle(placement, arrowSize, arrowRadius, arrowOffset, arrowPosi
32562
32101
  const [side, alignment] = placement.split('-');
32563
32102
  switch (side) {
32564
32103
  case 'top':
32104
+ // Arrow points down, hide the borders that overlap with content (top-left corner after rotation)
32565
32105
  return {
32566
32106
  ...base,
32107
+ borderTopWidth: 0,
32108
+ borderLeftWidth: 0,
32567
32109
  bottom: -arrowSize,
32568
32110
  left: alignment === 'end'
32569
32111
  ? `calc(100% - ${(arrowPosition === 'side' ? arrowOffset : arrowSize)}px)`
@@ -32573,8 +32115,11 @@ function getArrowStyle(placement, arrowSize, arrowRadius, arrowOffset, arrowPosi
32573
32115
  marginLeft: alignment || arrowPosition === 'side' ? 0 : -arrowSize,
32574
32116
  };
32575
32117
  case 'bottom':
32118
+ // Arrow points up, hide the borders that overlap with content (bottom-right corner after rotation)
32576
32119
  return {
32577
32120
  ...base,
32121
+ borderBottomWidth: 0,
32122
+ borderRightWidth: 0,
32578
32123
  top: -arrowSize,
32579
32124
  left: alignment === 'end'
32580
32125
  ? `calc(100% - ${(arrowPosition === 'side' ? arrowOffset : arrowSize)}px)`
@@ -32584,8 +32129,11 @@ function getArrowStyle(placement, arrowSize, arrowRadius, arrowOffset, arrowPosi
32584
32129
  marginLeft: alignment || arrowPosition === 'side' ? 0 : -arrowSize,
32585
32130
  };
32586
32131
  case 'left':
32132
+ // Arrow points right, hide the borders that overlap with content (bottom-left corner after rotation)
32587
32133
  return {
32588
32134
  ...base,
32135
+ borderBottomWidth: 0,
32136
+ borderLeftWidth: 0,
32589
32137
  right: -arrowSize,
32590
32138
  top: alignment === 'end'
32591
32139
  ? `calc(100% - ${(arrowPosition === 'side' ? arrowOffset : arrowSize)}px)`
@@ -32595,8 +32143,11 @@ function getArrowStyle(placement, arrowSize, arrowRadius, arrowOffset, arrowPosi
32595
32143
  marginTop: alignment || arrowPosition === 'side' ? 0 : -arrowSize,
32596
32144
  };
32597
32145
  case 'right':
32146
+ // Arrow points left, hide the borders that overlap with content (top-right corner after rotation)
32598
32147
  return {
32599
32148
  ...base,
32149
+ borderTopWidth: 0,
32150
+ borderRightWidth: 0,
32600
32151
  left: -arrowSize,
32601
32152
  top: alignment === 'end'
32602
32153
  ? `calc(100% - ${(arrowPosition === 'side' ? arrowOffset : arrowSize)}px)`
@@ -35731,104 +35282,6 @@ const Ring = factory((props, ref) => {
35731
35282
  return (jsxs(View, { ref: ref, style: [styles$8.container, spacingStyles, style], testID: testID, accessibilityLabel: accessibilityLabel !== null && accessibilityLabel !== void 0 ? accessibilityLabel : `Ring value ${Math.round(percent)} percent`, accessibilityRole: "progressbar", accessibilityValue: { min, max, now: Math.round(clampedValue) }, ...otherProps, children: [jsxs(View, { style: [styles$8.ringWrapper, { width: size, height: size }, ringStyle], children: [jsxs(Svg, { width: size, height: size, viewBox: `0 0 ${size} ${size}`, children: [jsx(Circle, { cx: size / 2, cy: size / 2, r: radius, stroke: defaultTrackColor, strokeWidth: thickness, fill: "transparent" }), jsx(Circle, { cx: size / 2, cy: size / 2, r: radius, stroke: resolvedProgressColor, strokeWidth: thickness, strokeLinecap: roundedCaps ? 'round' : 'butt', strokeDasharray: `${circumference} ${circumference}`, strokeDashoffset: dashOffset, fill: "transparent", transform: `rotate(-90 ${size / 2} ${size / 2})` })] }), jsx(View, { pointerEvents: "none", style: [styles$8.centerContent, { width: size, height: size }, contentStyle], children: centerContent })] }), caption !== undefined && caption !== null ? (React__default.isValidElement(caption) ? (caption) : (jsx(Text, { variant: "span", size: "xs", color: captionTextColor, weight: "600", style: [{ marginTop: 6, letterSpacing: 1 }, captionStyle], children: caption }))) : null] }));
35732
35283
  }, { displayName: 'Ring' });
35733
35284
 
35734
- const NAVIGATIONPROGRESS_DEFAULTS = {
35735
- size: 3,
35736
- color: 'primary',
35737
- zIndex: 9999,
35738
- overlay: true,
35739
- stepInterval: 500,
35740
- radius: 0,
35741
- };
35742
-
35743
- let subscribers = new Set();
35744
- let internalState = { value: 0, active: false };
35745
- let interval = null;
35746
- function broadcast() { subscribers.forEach(cb => cb({ ...internalState })); }
35747
- function schedule(intervalMs) {
35748
- clearInterval(interval);
35749
- interval = setInterval(() => {
35750
- if (!internalState.active)
35751
- return;
35752
- const remain = 100 - internalState.value;
35753
- const inc = Math.max(0.1, remain * 0.03);
35754
- internalState.value = Math.min(99, internalState.value + inc);
35755
- broadcast();
35756
- }, intervalMs);
35757
- }
35758
- const navigationProgress = {
35759
- start() { if (internalState.active)
35760
- return; internalState.active = true; if (internalState.value >= 100)
35761
- internalState.value = 0; broadcast(); schedule(NAVIGATIONPROGRESS_DEFAULTS.stepInterval); },
35762
- stop() { internalState.active = false; broadcast(); },
35763
- complete() { internalState.active = true; internalState.value = 100; broadcast(); setTimeout(() => { internalState.active = false; internalState.value = 0; broadcast(); }, 400); },
35764
- reset() { internalState.value = 0; internalState.active = false; broadcast(); },
35765
- set(v) { internalState.value = Math.max(0, Math.min(100, v)); broadcast(); },
35766
- increment(delta = 5) { internalState.value = Math.min(100, internalState.value + delta); broadcast(); },
35767
- decrement(delta = 5) { internalState.value = Math.max(0, internalState.value - delta); broadcast(); },
35768
- isActive() { return internalState.active; }
35769
- };
35770
- const NavigationProgress = ({ value, size = NAVIGATIONPROGRESS_DEFAULTS.size, color = NAVIGATIONPROGRESS_DEFAULTS.color, zIndex = NAVIGATIONPROGRESS_DEFAULTS.zIndex, overlay = NAVIGATIONPROGRESS_DEFAULTS.overlay, stepInterval = NAVIGATIONPROGRESS_DEFAULTS.stepInterval, radius = NAVIGATIONPROGRESS_DEFAULTS.radius, active = true, style }) => {
35771
- const theme = useTheme();
35772
- const scheme = useColorScheme();
35773
- const isDark = scheme === 'dark';
35774
- const progress = useSharedValue(0);
35775
- const opacity = useSharedValue(0);
35776
- useEffect(() => {
35777
- const sub = (s) => {
35778
- if (value == null) {
35779
- progress.value = withTiming(s.value, { duration: stepInterval });
35780
- opacity.value = withTiming(s.active ? 1 : 0, { duration: 150 });
35781
- }
35782
- };
35783
- subscribers.add(sub);
35784
- broadcast();
35785
- return () => { subscribers.delete(sub); };
35786
- }, [value, stepInterval, progress, opacity]);
35787
- useEffect(() => {
35788
- if (value != null) {
35789
- progress.value = withTiming(value, { duration: stepInterval });
35790
- opacity.value = withTiming(active ? 1 : 0, { duration: 150 });
35791
- }
35792
- }, [value, active, stepInterval]);
35793
- let resolvedColor = color;
35794
- if (theme.colors[color]) {
35795
- const bucket = theme.colors[color];
35796
- resolvedColor = bucket[5] || bucket[4] || bucket[0];
35797
- }
35798
- const barStyle = useAnimatedStyle(() => ({ width: `${progress.value}%` }));
35799
- const containerStyle = useAnimatedStyle(() => ({ opacity: opacity.value }));
35800
- return (jsxs(Animated.View, { style: [
35801
- {
35802
- position: overlay ? 'absolute' : 'relative',
35803
- top: 0,
35804
- left: 0,
35805
- right: 0,
35806
- height: size,
35807
- backgroundColor: isDark ? theme.colors.gray[3] : theme.colors.gray[2],
35808
- overflow: 'hidden',
35809
- zIndex,
35810
- borderRadius: radius,
35811
- pointerEvents: 'none'
35812
- },
35813
- containerStyle,
35814
- style
35815
- ], children: [jsx(Animated.View, { style: [{
35816
- position: 'absolute',
35817
- top: 0,
35818
- bottom: 0,
35819
- left: 0,
35820
- backgroundColor: resolvedColor,
35821
- borderRadius: radius,
35822
- }, barStyle] }), jsx(Animated.View, { style: [{
35823
- position: 'absolute',
35824
- top: 0,
35825
- bottom: 0,
35826
- right: 0,
35827
- width: 80,
35828
- backgroundColor: 'rgba(255,255,255,0.2)'
35829
- }, barStyle] })] }));
35830
- };
35831
-
35832
35285
  const DEFAULT_OPACITY = 0.6;
35833
35286
  const HEX_COLOR_REGEX = /^#?[0-9a-f]{3,8}$/i;
35834
35287
  const clampOpacity = (value) => {
@@ -36021,270 +35474,6 @@ const LoadingOverlay = React__default.forwardRef((props, ref) => {
36021
35474
  });
36022
35475
  LoadingOverlay.displayName = 'LoadingOverlay';
36023
35476
 
36024
- // A lightweight hover-activated floating panel similar to Mantine HoverCard
36025
- function HoverCardBase(props, ref) {
36026
- const { children, target, position = 'top', offset = 8, openDelay = 100, closeDelay = 150, opened: controlledOpened, shadow = 'md', radius = 'md', withinPortal = true, width, withArrow = false, closeOnEscape = true, onOpen, onClose, disabled = false, style, testID, zIndex = 3000, keepMounted = false, trigger = 'hover', } = props;
36027
- const [opened, setOpened] = useState(false);
36028
- const openTimeout = useRef(null);
36029
- const closeTimeout = useRef(null);
36030
- const containerRef = useRef(null);
36031
- const targetRef = useRef(null);
36032
- const overlayIdRef = useRef(null);
36033
- const overlayContentRef = useRef(null);
36034
- const isHoveringTargetRef = useRef(false);
36035
- const isHoveringOverlayRef = useRef(false);
36036
- const theme = useTheme();
36037
- const { openOverlay, closeOverlay, updateOverlay } = useOverlay();
36038
- const isOpened = controlledOpened !== undefined ? controlledOpened : opened;
36039
- const clearTimers = () => {
36040
- if (openTimeout.current) {
36041
- clearTimeout(openTimeout.current);
36042
- openTimeout.current = null;
36043
- }
36044
- if (closeTimeout.current) {
36045
- clearTimeout(closeTimeout.current);
36046
- closeTimeout.current = null;
36047
- }
36048
- };
36049
- const doOpen = useCallback(() => {
36050
- if (disabled)
36051
- return;
36052
- setOpened(true);
36053
- onOpen === null || onOpen === void 0 ? void 0 : onOpen();
36054
- }, [disabled, onOpen]);
36055
- const doClose = useCallback(() => {
36056
- setOpened(false);
36057
- onClose === null || onClose === void 0 ? void 0 : onClose();
36058
- }, [onClose]);
36059
- const scheduleOpen = useCallback(() => {
36060
- clearTimers();
36061
- openTimeout.current = setTimeout(doOpen, openDelay);
36062
- }, [doOpen, openDelay]);
36063
- const scheduleClose = useCallback(() => {
36064
- clearTimers();
36065
- closeTimeout.current = setTimeout(() => {
36066
- // Only close if neither target nor overlay are hovered (web)
36067
- if (Platform.OS === 'web') {
36068
- if (isHoveringTargetRef.current || isHoveringOverlayRef.current)
36069
- return;
36070
- }
36071
- doClose();
36072
- }, closeDelay);
36073
- }, [doClose, closeDelay]);
36074
- useEffect(() => () => clearTimers(), []);
36075
- // Escape key (web only)
36076
- useEffect(() => {
36077
- if (!closeOnEscape || Platform.OS !== 'web')
36078
- return;
36079
- const handler = (e) => { if (e.key === 'Escape')
36080
- doClose(); };
36081
- document.addEventListener('keydown', handler);
36082
- return () => document.removeEventListener('keydown', handler);
36083
- }, [closeOnEscape, doClose]);
36084
- const getInlinePositionStyle = () => {
36085
- const base = { position: 'absolute' };
36086
- switch (position) {
36087
- case 'top': return { ...base, bottom: '100%', left: 0, marginBottom: offset };
36088
- case 'bottom': return { ...base, top: '100%', left: 0, marginTop: offset };
36089
- case 'left': return { ...base, right: '100%', top: 0, marginRight: offset };
36090
- case 'right': return { ...base, left: '100%', top: 0, marginLeft: offset };
36091
- default: return { ...base, top: '100%', left: 0, marginTop: offset };
36092
- }
36093
- };
36094
- const shadowStyle = (() => {
36095
- switch (shadow) {
36096
- case 'sm':
36097
- return { boxShadow: '0 1px 2px rgba(0, 0, 0, 0.1)', elevation: 2 };
36098
- case 'md':
36099
- return { boxShadow: '0 2px 4px rgba(0, 0, 0, 0.15)', elevation: 4 };
36100
- case 'lg':
36101
- return { boxShadow: '0 4px 8px rgba(0, 0, 0, 0.2)', elevation: 8 };
36102
- default:
36103
- return {};
36104
- }
36105
- })();
36106
- const renderArrow = (placement) => {
36107
- if (!withArrow)
36108
- return null;
36109
- const base = { position: 'absolute', width: 0, height: 0 };
36110
- const color = theme.colors.gray[0];
36111
- const styles = {
36112
- top: { top: '100%', left: 12, borderLeftWidth: 6, borderRightWidth: 6, borderTopWidth: 6, borderLeftColor: 'transparent', borderRightColor: 'transparent', borderTopColor: color },
36113
- bottom: { bottom: '100%', left: 12, borderLeftWidth: 6, borderRightWidth: 6, borderBottomWidth: 6, borderLeftColor: 'transparent', borderRightColor: 'transparent', borderBottomColor: color },
36114
- left: { left: '100%', top: 12, borderTopWidth: 6, borderBottomWidth: 6, borderLeftWidth: 6, borderTopColor: 'transparent', borderBottomColor: 'transparent', borderLeftColor: color },
36115
- right: { right: '100%', top: 12, borderTopWidth: 6, borderBottomWidth: 6, borderRightWidth: 6, borderTopColor: 'transparent', borderBottomColor: 'transparent', borderRightColor: color },
36116
- };
36117
- const key = placement.split('-')[0];
36118
- return jsx(View, { style: { ...base, ...(styles[key] || styles.top) } });
36119
- };
36120
- const openPortal = useCallback(async () => {
36121
- if (!withinPortal || !isOpened || overlayIdRef.current)
36122
- return;
36123
- const rect = await measureElement(targetRef);
36124
- const estWidth = width || 240;
36125
- const estHeight = 160; // rough initial height
36126
- const pos = calculateOverlayPositionEnhanced(rect, { width: estWidth, height: estHeight }, {
36127
- placement: position,
36128
- offset,
36129
- viewport: getViewport(),
36130
- strategy: 'fixed'
36131
- });
36132
- const overlayContent = (jsxs(View, { ref: overlayContentRef, style: [
36133
- {
36134
- backgroundColor: theme.colors.gray[0],
36135
- borderRadius: getRadius$2(radius),
36136
- paddingHorizontal: getSpacing('md'),
36137
- paddingVertical: getSpacing('sm'),
36138
- borderWidth: 1,
36139
- borderColor: theme.colors.gray[3],
36140
- minWidth: width || 160,
36141
- maxWidth: width || 320,
36142
- },
36143
- shadowStyle,
36144
- ], ...(Platform.OS === 'web' && trigger === 'hover' ? {
36145
- onMouseEnter: () => { isHoveringOverlayRef.current = true; clearTimers(); },
36146
- onMouseLeave: () => { isHoveringOverlayRef.current = false; scheduleClose(); },
36147
- } : {}), children: [children, renderArrow(pos.placement)] }));
36148
- const id = openOverlay({
36149
- content: overlayContent,
36150
- anchor: { x: pos.x, y: pos.y, width: estWidth, height: estHeight },
36151
- trigger: trigger,
36152
- // For hover-triggered overlays, do NOT render a click-outside backdrop – it steals hover
36153
- // and immediately fires target onMouseLeave. We rely on pointer leave timers instead.
36154
- closeOnClickOutside: trigger !== 'hover',
36155
- closeOnEscape: closeOnEscape,
36156
- strategy: 'fixed',
36157
- onClose: () => { overlayIdRef.current = null; if (opened)
36158
- setOpened(false); onClose === null || onClose === void 0 ? void 0 : onClose(); },
36159
- zIndex
36160
- });
36161
- overlayIdRef.current = id;
36162
- }, [withinPortal, isOpened, overlayIdRef, position, offset, width, trigger, closeOnEscape, theme, radius, shadowStyle, children, opened, onClose, getSpacing, getRadius$2]);
36163
- const closePortal = useCallback(() => {
36164
- if (overlayIdRef.current) {
36165
- closeOverlay(overlayIdRef.current);
36166
- overlayIdRef.current = null;
36167
- }
36168
- }, [closeOverlay]);
36169
- useEffect(() => {
36170
- if (withinPortal) {
36171
- if (isOpened)
36172
- openPortal();
36173
- else
36174
- closePortal();
36175
- }
36176
- return () => { if (!isOpened)
36177
- closePortal(); };
36178
- }, [isOpened, withinPortal, openPortal, closePortal]);
36179
- useEffect(() => {
36180
- if (!withinPortal || Platform.OS !== 'web' || !isOpened || !overlayIdRef.current)
36181
- return;
36182
- const handler = () => {
36183
- Promise.all([measureElement(targetRef)]).then(([rect]) => {
36184
- var _a, _b;
36185
- const actualWidth = ((_a = overlayContentRef.current) === null || _a === void 0 ? void 0 : _a.offsetWidth) || width || 240;
36186
- const actualHeight = ((_b = overlayContentRef.current) === null || _b === void 0 ? void 0 : _b.offsetHeight) || 160;
36187
- const pos = calculateOverlayPositionEnhanced(rect, { width: actualWidth, height: actualHeight }, {
36188
- placement: position,
36189
- offset,
36190
- viewport: getViewport(),
36191
- strategy: 'fixed'
36192
- });
36193
- updateOverlay(overlayIdRef.current, { anchor: { x: pos.x, y: pos.y, width: actualWidth, height: actualHeight } });
36194
- });
36195
- };
36196
- window.addEventListener('resize', handler);
36197
- window.addEventListener('scroll', handler, true);
36198
- return () => {
36199
- window.removeEventListener('resize', handler);
36200
- window.removeEventListener('scroll', handler, true);
36201
- };
36202
- }, [withinPortal, isOpened, position, offset, width, updateOverlay]);
36203
- // Smart sizing: after content mounts, measure actual size and reposition if changed
36204
- useEffect(() => {
36205
- if (!withinPortal || !isOpened || !overlayIdRef.current)
36206
- return;
36207
- let frame;
36208
- const attempt = () => {
36209
- Promise.all([
36210
- measureElement(targetRef),
36211
- measureElement({ current: overlayContentRef.current })
36212
- ]).then(([targetRect, contentRect]) => {
36213
- if (!targetRect.width || !contentRect.width)
36214
- return; // skip invalid
36215
- const desiredWidth = width || contentRect.width;
36216
- const desiredHeight = contentRect.height;
36217
- // Recalculate with actual content size
36218
- const pos = calculateOverlayPositionEnhanced(targetRect, { width: desiredWidth, height: desiredHeight }, {
36219
- placement: position,
36220
- offset,
36221
- viewport: getViewport(),
36222
- strategy: 'fixed'
36223
- });
36224
- updateOverlay(overlayIdRef.current, { anchor: { x: pos.x, y: pos.y, width: desiredWidth, height: desiredHeight } });
36225
- });
36226
- };
36227
- // Delay a bit to allow layout
36228
- frame = setTimeout(attempt, 30);
36229
- return () => { if (frame)
36230
- clearTimeout(frame); };
36231
- }, [withinPortal, isOpened, position, offset, width, updateOverlay]);
36232
- const targetProps = {};
36233
- if (trigger === 'hover') {
36234
- if (Platform.OS === 'web') {
36235
- targetProps.onMouseEnter = () => { isHoveringTargetRef.current = true; scheduleOpen(); };
36236
- targetProps.onMouseLeave = () => { isHoveringTargetRef.current = false; scheduleClose(); };
36237
- }
36238
- else {
36239
- // fallback: tap to toggle on native
36240
- targetProps.onPress = () => {
36241
- if (isOpened) {
36242
- doClose();
36243
- }
36244
- else {
36245
- doOpen();
36246
- }
36247
- };
36248
- }
36249
- }
36250
- else if (trigger === 'click') {
36251
- targetProps.onPress = () => {
36252
- if (isOpened) {
36253
- doClose();
36254
- }
36255
- else {
36256
- doOpen();
36257
- }
36258
- };
36259
- }
36260
- const inlineContent = (isOpened || keepMounted) && !withinPortal ? (jsxs(View, { style: [
36261
- getInlinePositionStyle(),
36262
- {
36263
- backgroundColor: theme.colors.gray[0],
36264
- borderRadius: getRadius$2(radius),
36265
- paddingHorizontal: getSpacing('md'),
36266
- paddingVertical: getSpacing('sm'),
36267
- borderWidth: 1,
36268
- borderColor: theme.colors.gray[3],
36269
- minWidth: width,
36270
- zIndex,
36271
- },
36272
- shadowStyle,
36273
- ], pointerEvents: "auto", ...(Platform.OS === 'web' && trigger === 'hover' ? {
36274
- onMouseEnter: () => { isHoveringOverlayRef.current = true; clearTimers(); },
36275
- onMouseLeave: () => { isHoveringOverlayRef.current = false; scheduleClose(); },
36276
- } : {}), children: [children, renderArrow(position)] })) : null;
36277
- return (jsxs(View, { ref: ref, style: [{ position: 'relative', alignSelf: 'flex-start' }, style], testID: testID, children: [jsx(Pressable, { ref: (node) => { containerRef.current = node; targetRef.current = node; }, ...targetProps, style: ({ pressed }) => {
36278
- var _a;
36279
- return [
36280
- { opacity: pressed ? 0.85 : 1 },
36281
- (_a = target === null || target === void 0 ? void 0 : target.props) === null || _a === void 0 ? void 0 : _a.style,
36282
- ];
36283
- }, children: target }), inlineContent] }));
36284
- }
36285
- const HoverCard = factory(HoverCardBase);
36286
- HoverCard.displayName = 'HoverCard';
36287
-
36288
35477
  const ContextMenu = ({ children, items, closeOnSelect = true, longPressDelay = 350, maxHeight = 280, onOpen, onClose, open: controlledOpen, position: controlledPosition, portalId, style, }) => {
36289
35478
  var _a, _b;
36290
35479
  const [internalOpen, setInternalOpen] = useState(false);
@@ -39874,11 +39063,14 @@ QRCodeSVG.displayName = 'QRCodeSVG';
39874
39063
  */
39875
39064
  function QRCode(props) {
39876
39065
  var _a;
39066
+ const theme = useTheme();
39877
39067
  const { spacingProps, otherProps: propsAfterSpacing } = extractSpacingProps(props);
39878
39068
  const { layoutProps, otherProps } = extractLayoutProps(propsAfterSpacing);
39879
- const { value, size = 400, backgroundColor = 'transparent', color = '#000000', errorCorrectionLevel = 'M', quietZone = 4, logo, style, testID, accessibilityLabel, onError, onLoadStart, // deprecated noop
39069
+ const { value, size = 400, backgroundColor = 'transparent', color, errorCorrectionLevel = 'M', quietZone = 4, logo, style, testID, accessibilityLabel, onError, onLoadStart, // deprecated noop
39880
39070
  onLoadEnd, // deprecated noop
39881
39071
  ...rest } = otherProps;
39072
+ // Default color to theme's primary text color for dark mode support
39073
+ const resolvedColor = color !== null && color !== void 0 ? color : theme.text.primary;
39882
39074
  const { copy } = useClipboard();
39883
39075
  const toast = useToast();
39884
39076
  const shouldCopyOnPress = !!otherProps.copyOnPress;
@@ -39894,7 +39086,7 @@ function QRCode(props) {
39894
39086
  });
39895
39087
  }
39896
39088
  }, [copy, copyValue, toast, otherProps.copyToastMessage, otherProps.copyToastTitle]);
39897
- const content = (jsxs(View, { style: { borderRadius: 8, overflow: 'hidden' }, children: [jsx(QRCodeSVG, { value: value, size: size, maxWidth: '100%', backgroundColor: backgroundColor, color: color, errorCorrectionLevel: errorCorrectionLevel, quietZone: quietZone, logo: logo, style: style, testID: testID, accessibilityLabel: accessibilityLabel, onError: onError, ...spacingProps, ...layoutProps, ...rest }), otherProps.showCopyButton && (jsx(CopyButton, { value: copyValue, iconOnly: true, size: "sm", style: { position: 'absolute', top: 8, right: 8 }, onCopy: () => { } }))] }));
39089
+ const content = (jsxs(View, { style: { borderRadius: 8, overflow: 'hidden' }, children: [jsx(QRCodeSVG, { value: value, size: size, maxWidth: '100%', backgroundColor: backgroundColor, color: resolvedColor, errorCorrectionLevel: errorCorrectionLevel, quietZone: quietZone, logo: logo, style: style, testID: testID, accessibilityLabel: accessibilityLabel, onError: onError, ...spacingProps, ...layoutProps, ...rest }), otherProps.showCopyButton && (jsx(CopyButton, { value: copyValue, iconOnly: true, size: "sm", style: { position: 'absolute', top: 8, right: 8 }, onCopy: () => { } }))] }));
39898
39090
  if (shouldCopyOnPress) {
39899
39091
  return (jsx(Pressable, { onPress: handleCopy, accessibilityLabel: accessibilityLabel || 'QR code', children: content }));
39900
39092
  }
@@ -41967,5 +41159,5 @@ function withPressAnimation(Component, animationProps) {
41967
41159
  */
41968
41160
  const AnimatedPressable = PressAnimation;
41969
41161
 
41970
- export { AbilityCore, AccessibilityProvider, Accordion, AmazonAppstoreBadge, AmazonAppstoreButton, AmazonMusicListenBadge, AmazonPrimeVideoBadge, AmazonStoreBadge, AnimatedPressable, AppLayoutProvider, AppLayoutRenderer, AppShell, AppShellAside, AppShellBottomNav, AppShellFooter, AppShellHeader, AppShellMain, AppShellNavbar, AppShellSection, AppStoreBadge, AppStoreButton, AppStoreDownloadBadge, AppleAppStoreButton, AppleMusicListenBadge, ApplePodcastsListenBadge, AutoComplete, Avatar, AvatarGroup, Badge, Block, Blockquote, Bold, BottomAppBar, BrandButton, BrandIcon, Breadcrumbs, Button, COMPONENT_SIZES$1 as COMPONENT_SIZES, COMPONENT_SIZE_ORDER, Calendar, Can, CanWithConditions, Cannot, Card, Carousel, Checkbox, Chip, ChromeWebStoreBadge, Cite, Code, CodeBlock, Collapse, ColorPicker, ColorSwatch, Column, ComponentWithDisclaimer, ContextMenu, CopyButton, DARK_THEME, DEFAULT_BREAKPOINTS, DEFAULT_COMPONENT_SIZE, DEFAULT_SOUND_IDS, DEFAULT_THEME, DataTable, DatePicker, DatePickerInput, Day, Dialog, DialogProvider, DialogRenderer, DirectionProvider, Disclaimer, DiscordJoinBadge, Divider, EmojiPicker, Emphasis, FDroidButton, FileInput, Flex, FloatingActions, Form, GalaxyStoreDownloadBadge, Gallery, Gauge, GitHubViewBadge, GooglePlayButton, GooglePlayDownloadBadge, Grid, GridItem, H1, H2, H3, H4, H5, H6, HapticsProvider, Heading1, Heading2, Heading3, Heading4, Heading5, Heading6, Highlight, HoverCard, HuaweiAppGalleryBadge, I18nProvider, Icon, IconButton, Image, Indicator, Input, Italic, Kbd, KeyCap, KeyboardAwareLayout, KeyboardManagerProvider, Knob, Link, ListGroup, ListGroupBody, ListGroupDivider, ListGroupItem, Loader, LoadingOverlay, Lottie, MacAppStoreButton, Mark, Markdown, Masonry, Menu, MenuDivider, MenuDropdown, MenuItem, MenuItemButton, MenuLabel, MicrosoftStoreButton, MicrosoftStoreDownloadBadge, MiniCalendar, Month, MonthPicker, MonthPickerInput, NavigationProgress, Notice, NumberInput, Overlay, OverlayProvider, P, Pagination, PasswordInput, PermissionBuilder, PermissionGate, PermissionPatterns, PermissionProvider, PhoneInput, PinInput, PlatformBlocksProvider, Popover, PressAnimation, Progress, QRCode, Radio, RadioGroup, RangeSlider, Rating, RedditJoinBadge, RichTextEditor, Ring, RoleBuilder, Row, SIZE_SCALES, Search, SegmentedControl, Select, ShimmerText, Skeleton, Slider, Small, SoundCloudListenBadge, SoundProvider, Space, Spoiler, SpotifyListenBadge, Spotlight, SpotlightProvider, StatusBarManager, StepperWithSubComponents as Stepper, Strong, Sub, Sup, Switch, Table, TableOfContents, Tabs, Text, TextArea, TextInputBase, ThemeModeProvider, TikTokWatchBadge, TimePickerInput as TimePicker, TimePickerInput, TimelineWithItems as Timeline, Title, TitleRegistryProvider, Toast, ToastProvider, ToggleBar, ToggleButton, ToggleGroup, Tooltip, Tree, TwitchWatchBadge, Underline, Video, Waveform, YearPicker, YearPickerInput, YouTubeMusicListenBadge, YouTubeWatchBadge, calculateOverlayPositionEnhanced, clampComponentSize, clearOverlayPositionCache, createSound, createSpotlightStore, createTheme, debounce$1 as debounce, defineAbility, defineAppLayout, defineRoleAbility, directSpotlight, extractDisclaimerProps, factory, getAllSounds, getColor, getFontSize, getHeight, getIconSize$1 as getIconSize, getLineHeight, getRadius$1 as getRadius, getScrollPosition, getShadow$1 as getShadow, getSize, getSoundsByCategory, getSpacing, getViewport, globalHotkeys, measureAsyncPerformance, measureElement, measurePerformance, navigationProgress, onDialogsRequested, onSpotlightRequested, onToastsRequested, permissions, pointInRect, polymorphicFactory, px, rem, resolveComponentSize, resolveResponsiveProp, resolveResponsiveValue, resolveSize, spotlight, throttle, useAbility, useAccessibility, useAppLayoutContext, useAppShell, useAppShellApi, useAppShellLayout, useBreakpoint, useColorScheme, useDialog, useDialogApi, useDialogs, useDirectSpotlightState, useDirection, useDirectionSafe, useDisclaimer, useDropdownPositioning, useEscapeKey, useFormContext, useGlobalHotkeys, useHaptics, useHapticsSettings, useHotkeys, useI18n, useKeyboardManager, useKeyboardManagerOptional, useNavbarHover, useOptionalFormContext, useOverlay, useOverlayApi, useOverlays, usePermissions, usePopoverPositioning, useSimpleDialog, useSound, useSpotlightStore, useSpotlightStoreInstance, useSpotlightToggle, useTheme, useThemeMode, useTitleRegistry, useTitleRegistryOptional, useToast, useToastApi, useToggleColorScheme, useTooltipPositioning, withCan, withCannot, withDisclaimer, withPressAnimation };
41162
+ export { AccessibilityProvider, Accordion, AmazonAppstoreBadge, AmazonAppstoreButton, AmazonMusicListenBadge, AmazonPrimeVideoBadge, AmazonStoreBadge, AnimatedPressable, AppLayoutProvider, AppLayoutRenderer, AppShell, AppShellAside, AppShellBottomNav, AppShellFooter, AppShellHeader, AppShellMain, AppShellNavbar, AppShellSection, AppStoreBadge, AppStoreButton, AppStoreDownloadBadge, AppleAppStoreButton, AppleMusicListenBadge, ApplePodcastsListenBadge, AutoComplete, Avatar, AvatarGroup, Badge, Block, Blockquote, Bold, BottomAppBar, BrandButton, BrandIcon, Breadcrumbs, Button, COMPONENT_SIZES$1 as COMPONENT_SIZES, COMPONENT_SIZE_ORDER, Calendar, Card, Carousel, Checkbox, Chip, ChromeWebStoreBadge, Cite, Code, CodeBlock, Collapse, ColorPicker, ColorSwatch, Column, ComponentWithDisclaimer, ContextMenu, CopyButton, DARK_THEME, DEFAULT_BREAKPOINTS, DEFAULT_COMPONENT_SIZE, DEFAULT_SOUND_IDS, DEFAULT_THEME, DataTable, DatePicker, DatePickerInput, Day, Dialog, DialogProvider, DialogRenderer, DirectionProvider, Disclaimer, DiscordJoinBadge, Divider, EmojiPicker, Emphasis, FDroidButton, FileInput, Flex, FloatingActions, Form, GalaxyStoreDownloadBadge, Gallery, Gauge, GitHubViewBadge, GooglePlayButton, GooglePlayDownloadBadge, Grid, GridItem, H1, H2, H3, H4, H5, H6, HapticsProvider, Heading1, Heading2, Heading3, Heading4, Heading5, Heading6, Highlight, HuaweiAppGalleryBadge, I18nProvider, Icon, IconButton, Image, Indicator, Input, Italic, Kbd, KeyCap, KeyboardAwareLayout, KeyboardManagerProvider, Knob, Link, ListGroup, ListGroupBody, ListGroupDivider, ListGroupItem, Loader, LoadingOverlay, Lottie, MacAppStoreButton, Mark, Markdown, Masonry, Menu, MenuDivider, MenuDropdown, MenuItem, MenuItemButton, MenuLabel, MicrosoftStoreButton, MicrosoftStoreDownloadBadge, MiniCalendar, Month, MonthPicker, MonthPickerInput, Notice, NumberInput, Overlay, OverlayProvider, P, Pagination, PasswordInput, PhoneInput, PinInput, PlatformBlocksProvider, Popover, PressAnimation, Progress, QRCode, Radio, RadioGroup, RangeSlider, Rating, RedditJoinBadge, RichTextEditor, Ring, Row, SIZE_SCALES, Search, SegmentedControl, Select, ShimmerText, Skeleton, Slider, Small, SoundCloudListenBadge, SoundProvider, Space, Spoiler, SpotifyListenBadge, Spotlight, SpotlightProvider, StatusBarManager, StepperWithSubComponents as Stepper, Strong, Sub, Sup, Switch, Table, TableOfContents, Tabs, Text, TextArea, TextInputBase, ThemeModeProvider, TikTokWatchBadge, TimePickerInput as TimePicker, TimePickerInput, TimelineWithItems as Timeline, Title, TitleRegistryProvider, Toast, ToastProvider, ToggleBar, ToggleButton, ToggleGroup, Tooltip, Tree, TwitchWatchBadge, Underline, Video, Waveform, YearPicker, YearPickerInput, YouTubeMusicListenBadge, YouTubeWatchBadge, calculateOverlayPositionEnhanced, clampComponentSize, clearOverlayPositionCache, createSound, createSpotlightStore, createTheme, debounce$1 as debounce, defineAppLayout, directSpotlight, extractDisclaimerProps, factory, getAllSounds, getColor, getFontSize, getHeight, getIconSize$1 as getIconSize, getLineHeight, getRadius$1 as getRadius, getScrollPosition, getShadow$1 as getShadow, getSize, getSoundsByCategory, getSpacing, getViewport, globalHotkeys, measureAsyncPerformance, measureElement, measurePerformance, onDialogsRequested, onSpotlightRequested, onToastsRequested, pointInRect, polymorphicFactory, px, rem, resolveComponentSize, resolveResponsiveProp, resolveResponsiveValue, resolveSize, spotlight, throttle, useAccessibility, useAppLayoutContext, useAppShell, useAppShellApi, useAppShellLayout, useBreakpoint, useColorScheme, useDialog, useDialogApi, useDialogs, useDirectSpotlightState, useDirection, useDirectionSafe, useDisclaimer, useDropdownPositioning, useEscapeKey, useFormContext, useGlobalHotkeys, useHaptics, useHapticsSettings, useHotkeys, useI18n, useKeyboardManager, useKeyboardManagerOptional, useNavbarHover, useOptionalFormContext, useOverlay, useOverlayApi, useOverlays, usePopoverPositioning, useSimpleDialog, useSound, useSpotlightStore, useSpotlightStoreInstance, useSpotlightToggle, useTheme, useThemeMode, useTitleRegistry, useTitleRegistryOptional, useToast, useToastApi, useToggleColorScheme, useTooltipPositioning, withDisclaimer, withPressAnimation };
41971
41163
  //# sourceMappingURL=index.js.map