@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/cjs/index.js CHANGED
@@ -1184,25 +1184,50 @@ function OverlayRenderer({ style } = {}) {
1184
1184
  }) }));
1185
1185
  }
1186
1186
  function OverlayContent({ overlay, isTopmost, onBackdropPress }) {
1187
- var _a, _b, _c;
1187
+ var _a, _b, _c, _d, _e, _f;
1188
1188
  useTheme();
1189
1189
  const DEBUG = overlay.debug === true;
1190
1190
  if (DEBUG) {
1191
1191
  console.log('Rendering overlay content:');
1192
1192
  console.log('- anchor:', overlay.anchor);
1193
+ console.log('- placement:', overlay.placement);
1193
1194
  console.log('- strategy:', overlay.strategy);
1194
1195
  console.log('- zIndex:', overlay.zIndex);
1195
1196
  }
1197
+ // Check if this is a top-positioned overlay
1198
+ const isTopPlacement = (_a = overlay.placement) === null || _a === void 0 ? void 0 : _a.startsWith('top');
1199
+ // For top placements, we need to anchor from the bottom of the overlay
1200
+ // The anchor.y represents where the bottom of the overlay should be (top of the trigger minus offset)
1201
+ // So we use the estimated height to calculate where the top of the overlay would be
1196
1202
  const overlayStyle = {
1197
1203
  // Use fixed positioning on web for viewport-anchored overlays
1198
1204
  position: (reactNative.Platform.OS === 'web' && overlay.strategy === 'fixed') ? 'fixed' : 'absolute',
1199
- top: ((_a = overlay.anchor) === null || _a === void 0 ? void 0 : _a.y) || 0,
1200
1205
  left: ((_b = overlay.anchor) === null || _b === void 0 ? void 0 : _b.x) || 0,
1201
1206
  zIndex: overlay.zIndex,
1202
1207
  width: overlay.width || (((_c = overlay.anchor) === null || _c === void 0 ? void 0 : _c.width) ? overlay.anchor.width : undefined),
1203
1208
  maxWidth: overlay.maxWidth,
1204
1209
  maxHeight: overlay.maxHeight,
1205
1210
  };
1211
+ if (isTopPlacement && reactNative.Platform.OS === 'web') {
1212
+ // For top placements, position from the bottom of the overlay
1213
+ // anchor.y is where the top of the overlay should be, but we want to anchor from the bottom
1214
+ // so the overlay can grow upward naturally
1215
+ // The "bottom" of the anchor point is: viewport.height - (anchor.y + estimatedHeight)
1216
+ // But since we don't know actual height, use bottom anchoring relative to the trigger
1217
+ // Actually, anchor.y already accounts for the estimated height, so:
1218
+ // anchor.y = trigger.y - estimatedHeight - offset
1219
+ // We want the overlay's bottom edge to be at: trigger.y - offset
1220
+ // Which means: bottom = viewport.height - (trigger.y - offset) = viewport.height - anchor.y - estimatedHeight
1221
+ // Simpler: just set top and let it render, but the issue is the estimate is wrong
1222
+ // Better approach: use the anchor.y + anchor.height as the "bottom anchor point"
1223
+ // This is where the bottom of the overlay should be
1224
+ 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);
1225
+ overlayStyle.top = undefined;
1226
+ overlayStyle.bottom = `calc(100vh - ${bottomAnchorPoint}px)`;
1227
+ }
1228
+ else {
1229
+ overlayStyle.top = ((_f = overlay.anchor) === null || _f === void 0 ? void 0 : _f.y) || 0;
1230
+ }
1206
1231
  if (DEBUG) {
1207
1232
  console.log('- overlayStyle:', overlayStyle);
1208
1233
  }
@@ -6173,31 +6198,29 @@ const Button = (allProps) => {
6173
6198
  const { getDuration } = useReducedMotion$1();
6174
6199
  const { announce } = useAnnouncer();
6175
6200
  const { ref: focusRef} = useFocus(`button-${title || 'button'}`);
6201
+ // Track measured width for loading state preservation
6202
+ const [measuredWidth, setMeasuredWidth] = React.useState(null);
6203
+ const wasLoadingRef = React.useRef(loading);
6204
+ // When loading starts, we want to preserve the current measured width
6205
+ // When loading ends, clear the preserved width so it can resize naturally
6206
+ React.useEffect(() => {
6207
+ if (!loading && wasLoadingRef.current) {
6208
+ // Loading just ended, allow width to be recalculated
6209
+ setMeasuredWidth(null);
6210
+ }
6211
+ wasLoadingRef.current = loading;
6212
+ }, [loading]);
6213
+ const handleLayout = React.useCallback((event) => {
6214
+ // Only update measured width when not loading, so we capture the natural content width
6215
+ if (!loading) {
6216
+ const { width } = event.nativeEvent.layout;
6217
+ setMeasuredWidth(width);
6218
+ }
6219
+ // Call user's onLayout if provided
6220
+ onLayout === null || onLayout === void 0 ? void 0 : onLayout(event);
6221
+ }, [loading, onLayout]);
6176
6222
  // Determine button content - children takes precedence over title
6177
6223
  const buttonContent = children !== null && children !== void 0 ? children : title;
6178
- // Calculate minimum width for loading state based on content and size
6179
- const calculateMinWidth = () => {
6180
- if (!buttonContent || typeof buttonContent !== 'string')
6181
- return undefined;
6182
- // Base character width estimates based on size
6183
- const charWidthBySize = {
6184
- xs: 6,
6185
- sm: 7,
6186
- md: 8,
6187
- lg: 9,
6188
- xl: 10,
6189
- '2xl': 11,
6190
- '3xl': 12
6191
- };
6192
- const sizeKey = typeof size === 'string' ? size : 'md';
6193
- const charWidth = charWidthBySize[sizeKey] || 8;
6194
- const horizontalPadding = getSpacing(size) * 2; // Left + right padding
6195
- // Estimate content width: character count * average char width + padding
6196
- const contentWidth = buttonContent.length * charWidth + horizontalPadding;
6197
- // Add space for loader and gap when loading
6198
- const loaderWidth = getFontSize$1(size) + getSpacing(size) / 2; // Loader + margin
6199
- return Math.max(contentWidth, contentWidth + loaderWidth);
6200
- };
6201
6224
  // Determine what content to show based on loading state
6202
6225
  const displayContent = loading
6203
6226
  ? (loadingTitle !== undefined ? loadingTitle : '')
@@ -6236,12 +6259,12 @@ const Button = (allProps) => {
6236
6259
  const shadowStyles = getShadowStyles({ shadow: effectiveShadow }, theme, 'button');
6237
6260
  const spacingStyles = getSpacingStyles(spacingProps);
6238
6261
  const baseLayoutStyles = getLayoutStyles(layoutProps);
6239
- // Apply minimum width when loading to prevent size changes
6240
- const calculatedMinWidth = calculateMinWidth();
6262
+ // Apply measured width when loading to prevent size changes
6263
+ // Use the actual measured width instead of character-based estimates
6241
6264
  const layoutStyles = {
6242
6265
  ...baseLayoutStyles,
6243
- ...(loading && calculatedMinWidth && !layoutProps.width && !layoutProps.w && !layoutProps.fullWidth
6244
- ? { minWidth: calculatedMinWidth }
6266
+ ...(loading && measuredWidth && !layoutProps.width && !layoutProps.w && !layoutProps.fullWidth
6267
+ ? { width: measuredWidth, minWidth: measuredWidth }
6245
6268
  : {})
6246
6269
  };
6247
6270
  const iconSpacing = getSpacing(size) / 2;
@@ -6434,7 +6457,7 @@ const Button = (allProps) => {
6434
6457
  ...(reactNative.Platform.OS !== 'web' ? { transform: [{ translateY: 1 }] } : {})
6435
6458
  } : null,
6436
6459
  style,
6437
- ], onPress: handleInternalPress, onLayout: onLayout, onPressIn: handlePressIn, onPressOut: handlePressOut, onHoverIn: onHoverIn, onHoverOut: onHoverOut, onLongPress: onLongPress, disabled: isInteractionDisabled, children: [variant === 'gradient' && hasLinearGradient$4 && (jsxRuntime.jsx(OptionalLinearGradient$6, { colors: resolvedCustomColor
6460
+ ], onPress: handleInternalPress, onLayout: handleLayout, onPressIn: handlePressIn, onPressOut: handlePressOut, onHoverIn: onHoverIn, onHoverOut: onHoverOut, onLongPress: onLongPress, disabled: isInteractionDisabled, children: [variant === 'gradient' && hasLinearGradient$4 && (jsxRuntime.jsx(OptionalLinearGradient$6, { colors: resolvedCustomColor
6438
6461
  ? [resolvedCustomColor, theme.colors.primary[7]]
6439
6462
  : [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 ? (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx(Loader, { size: size, color: getLoaderColor(), style: !isIconButton ? { marginRight: iconSpacing } : undefined }), !isIconButton && renderButtonContent(displayContent)] })) : isIconButton ? (
6440
6463
  // Icon-only button
@@ -9752,593 +9775,6 @@ function UniversalCSS() {
9752
9775
  return null;
9753
9776
  }
9754
9777
 
9755
- /**
9756
- * Core Ability class for managing permissions
9757
- */
9758
- class AbilityCore {
9759
- constructor(rules = []) {
9760
- this.rules = [];
9761
- this.cache = new Map();
9762
- this.rules = [...rules];
9763
- }
9764
- /**
9765
- * Check if action is allowed on subject
9766
- */
9767
- can(action, subject, field) {
9768
- return this.check(action, subject, field).allowed;
9769
- }
9770
- /**
9771
- * Check if action is forbidden on subject
9772
- */
9773
- cannot(action, subject, field) {
9774
- return !this.can(action, subject, field);
9775
- }
9776
- /**
9777
- * Get detailed permission check result
9778
- */
9779
- check(action, subject, field) {
9780
- const cacheKey = this.getCacheKey(action, subject, field);
9781
- if (this.cache.has(cacheKey)) {
9782
- return this.cache.get(cacheKey);
9783
- }
9784
- const result = this.performCheck(action, subject, field);
9785
- this.cache.set(cacheKey, result);
9786
- return result;
9787
- }
9788
- /**
9789
- * Update ability rules and clear cache
9790
- */
9791
- update(rules) {
9792
- this.rules = [...rules];
9793
- this.cache.clear();
9794
- }
9795
- /**
9796
- * Get all current rules
9797
- */
9798
- getRules() {
9799
- return [...this.rules];
9800
- }
9801
- /**
9802
- * Clear all rules and cache
9803
- */
9804
- clear() {
9805
- this.rules = [];
9806
- this.cache.clear();
9807
- }
9808
- /**
9809
- * Perform the actual permission check
9810
- */
9811
- performCheck(action, subject, field) {
9812
- // Start with denied by default
9813
- let result = {
9814
- allowed: false,
9815
- reason: 'No matching permission rule found'
9816
- };
9817
- // Check rules in order (later rules can override earlier ones)
9818
- for (const rule of this.rules) {
9819
- if (this.ruleMatches(rule, action, subject, field)) {
9820
- result = {
9821
- allowed: !rule.inverted,
9822
- reason: rule.reason || (rule.inverted ? 'Access denied by rule' : 'Access granted by rule'),
9823
- rule
9824
- };
9825
- }
9826
- }
9827
- return result;
9828
- }
9829
- /**
9830
- * Check if a rule matches the current permission check
9831
- */
9832
- ruleMatches(rule, action, subject, field) {
9833
- // Check action match
9834
- const actions = Array.isArray(rule.action) ? rule.action : [rule.action];
9835
- if (!actions.includes(action) && !actions.includes('*')) {
9836
- return false;
9837
- }
9838
- // Check subject match
9839
- const subjects = Array.isArray(rule.subject) ? rule.subject : [rule.subject];
9840
- if (!this.subjectMatches(subjects, subject)) {
9841
- return false;
9842
- }
9843
- // Check field match (if specified)
9844
- if (field && rule.fields && !rule.fields.includes(field)) {
9845
- return false;
9846
- }
9847
- // Check conditions (if subject is an object)
9848
- if (rule.conditions && typeof subject === 'object' && subject !== null) {
9849
- return this.conditionsMatch(rule.conditions, subject);
9850
- }
9851
- return true;
9852
- }
9853
- /**
9854
- * Check if subject matches any of the rule subjects
9855
- */
9856
- subjectMatches(ruleSubjects, checkSubject) {
9857
- for (const ruleSubject of ruleSubjects) {
9858
- if (ruleSubject === '*')
9859
- return true;
9860
- if (ruleSubject === checkSubject)
9861
- return true;
9862
- // Handle class/constructor matching
9863
- if (typeof ruleSubject === 'function' && typeof checkSubject === 'object') {
9864
- if (checkSubject instanceof ruleSubject)
9865
- return true;
9866
- if (checkSubject.constructor === ruleSubject)
9867
- return true;
9868
- }
9869
- // Handle string matching for object types
9870
- if (typeof ruleSubject === 'string' && typeof checkSubject === 'object') {
9871
- if (checkSubject.__type === ruleSubject)
9872
- return true;
9873
- if (checkSubject.type === ruleSubject)
9874
- return true;
9875
- if (checkSubject.constructor.name === ruleSubject)
9876
- return true;
9877
- }
9878
- }
9879
- return false;
9880
- }
9881
- /**
9882
- * Check if conditions match the subject object
9883
- */
9884
- conditionsMatch(conditions, subject) {
9885
- for (const [key, expectedValue] of Object.entries(conditions)) {
9886
- const actualValue = subject[key];
9887
- if (!this.valuesMatch(actualValue, expectedValue)) {
9888
- return false;
9889
- }
9890
- }
9891
- return true;
9892
- }
9893
- /**
9894
- * Check if two values match (handles various comparison types)
9895
- */
9896
- valuesMatch(actual, expected) {
9897
- // Exact match
9898
- if (actual === expected)
9899
- return true;
9900
- // Array contains check
9901
- if (Array.isArray(expected) && expected.includes(actual))
9902
- return true;
9903
- if (Array.isArray(actual) && actual.includes(expected))
9904
- return true;
9905
- // Function/predicate check
9906
- if (typeof expected === 'function') {
9907
- return expected(actual);
9908
- }
9909
- // Regex match for strings
9910
- if (expected instanceof RegExp && typeof actual === 'string') {
9911
- return expected.test(actual);
9912
- }
9913
- // Object comparison (shallow)
9914
- if (typeof expected === 'object' && typeof actual === 'object' && expected !== null && actual !== null) {
9915
- return Object.keys(expected).every(key => this.valuesMatch(actual[key], expected[key]));
9916
- }
9917
- return false;
9918
- }
9919
- /**
9920
- * Generate cache key for permission check
9921
- */
9922
- getCacheKey(action, subject, field) {
9923
- const subjectKey = typeof subject === 'object'
9924
- ? JSON.stringify(subject)
9925
- : String(subject);
9926
- return `${action}:${subjectKey}:${field || ''}`;
9927
- }
9928
- }
9929
- /**
9930
- * Create a new Ability instance
9931
- */
9932
- function createAbility(rules = []) {
9933
- return new AbilityCore(rules);
9934
- }
9935
-
9936
- /**
9937
- * Permission Context
9938
- */
9939
- const PermissionContext = React.createContext(null);
9940
- /**
9941
- * Permission Provider Component
9942
- */
9943
- const PermissionProvider = ({ rules = [], user, children, dev = {} }) => {
9944
- const [ability] = React.useState(() => createAbility(rules));
9945
- const [currentUser, setCurrentUser] = React.useState(user);
9946
- const updateAbility = React.useCallback((newRules) => {
9947
- ability.update(newRules);
9948
- }, [ability]);
9949
- const can = React.useCallback((action, subject, field) => {
9950
- const result = ability.can(action, subject, field);
9951
- if (dev.logChecks) {
9952
- console.log(`[Permissions] Can ${action} ${subject}${field ? `.${field}` : ''}:`, result);
9953
- }
9954
- return result;
9955
- }, [ability, dev.logChecks]);
9956
- const cannot = React.useCallback((action, subject, field) => {
9957
- const result = ability.cannot(action, subject, field);
9958
- if (dev.logChecks) {
9959
- console.log(`[Permissions] Cannot ${action} ${subject}${field ? `.${field}` : ''}:`, result);
9960
- }
9961
- return result;
9962
- }, [ability, dev.logChecks]);
9963
- const setUser = React.useCallback((newUser) => {
9964
- setCurrentUser(newUser);
9965
- }, []);
9966
- const contextValue = React.useMemo(() => ({
9967
- ability,
9968
- updateAbility,
9969
- can,
9970
- cannot,
9971
- user: currentUser,
9972
- setUser
9973
- }), [ability, updateAbility, can, cannot, currentUser, setUser]);
9974
- return React.createElement(PermissionContext.Provider, { value: contextValue }, children);
9975
- };
9976
- /**
9977
- * Hook to access permission context
9978
- */
9979
- const usePermissions = (options = {}) => {
9980
- const context = React.useContext(PermissionContext);
9981
- if (!context) {
9982
- if (options.required) {
9983
- throw new Error('usePermissions must be used within a PermissionProvider');
9984
- }
9985
- if (options.debug) {
9986
- console.warn('[Permissions] usePermissions called outside of PermissionProvider');
9987
- }
9988
- // Return a fallback context that allows everything
9989
- return {
9990
- ability: createAbility([{ action: '*', subject: '*' }]),
9991
- updateAbility: () => { },
9992
- can: () => true,
9993
- cannot: () => false,
9994
- user: null,
9995
- setUser: () => { }
9996
- };
9997
- }
9998
- return context;
9999
- };
10000
- /**
10001
- * Hook to get the current ability instance
10002
- */
10003
- const useAbility = () => {
10004
- const { ability } = usePermissions();
10005
- return ability;
10006
- };
10007
-
10008
- /**
10009
- * Can Component - Renders children if permission is granted
10010
- */
10011
- const Can = ({ I: action, a: subject, field, children, fallback = null, ability: customAbility, style, testID, passthrough = false }) => {
10012
- const { ability: contextAbility } = usePermissions();
10013
- const ability = customAbility || contextAbility;
10014
- // Development passthrough
10015
- if (passthrough && __DEV__) {
10016
- return React.createElement(reactNative.View, { style, testID }, children);
10017
- }
10018
- const hasPermission = subject
10019
- ? ability.can(action, subject, field)
10020
- : ability.can(action, '*', field);
10021
- if (hasPermission) {
10022
- return React.createElement(reactNative.View, { style, testID }, children);
10023
- }
10024
- return React.createElement(reactNative.View, { style, testID }, fallback);
10025
- };
10026
- /**
10027
- * Can Component with conditions - For object-level permissions
10028
- */
10029
- const CanWithConditions = ({ I: action, this: subject, field, children, fallback = null, ability: customAbility, style, testID, passthrough = false }) => {
10030
- const { ability: contextAbility } = usePermissions();
10031
- const ability = customAbility || contextAbility;
10032
- // Development passthrough
10033
- if (passthrough && __DEV__) {
10034
- return React.createElement(reactNative.View, { style, testID }, children);
10035
- }
10036
- const hasPermission = ability.can(action, subject, field);
10037
- if (hasPermission) {
10038
- return React.createElement(reactNative.View, { style, testID }, children);
10039
- }
10040
- return React.createElement(reactNative.View, { style, testID }, fallback);
10041
- };
10042
- /**
10043
- * Cannot Component - Renders children if permission is NOT granted
10044
- */
10045
- const Cannot = ({ I: action, a: subject, field, children, fallback = null, ability: customAbility, style, testID, passthrough = false }) => {
10046
- const { ability: contextAbility } = usePermissions();
10047
- const ability = customAbility || contextAbility;
10048
- // Development passthrough
10049
- if (passthrough && __DEV__) {
10050
- return React.createElement(reactNative.View, { style, testID }, fallback);
10051
- }
10052
- const hasPermission = subject
10053
- ? ability.can(action, subject, field)
10054
- : ability.can(action, '*', field);
10055
- if (!hasPermission) {
10056
- return React.createElement(reactNative.View, { style, testID }, children);
10057
- }
10058
- return React.createElement(reactNative.View, { style, testID }, fallback);
10059
- };
10060
- /**
10061
- * Permission Gate - Requires ALL permissions to pass
10062
- */
10063
- const PermissionGate = ({ permissions, children, fallback = null, ability: customAbility, onUnauthorized }) => {
10064
- const { ability: contextAbility } = usePermissions();
10065
- const ability = customAbility || contextAbility;
10066
- const allPermissionsGranted = permissions.every(({ action, subject, field }) => ability.can(action, subject, field));
10067
- React.useEffect(() => {
10068
- if (!allPermissionsGranted && onUnauthorized) {
10069
- onUnauthorized();
10070
- }
10071
- }, [allPermissionsGranted, onUnauthorized]);
10072
- if (allPermissionsGranted) {
10073
- return React.createElement(React.Fragment, null, children);
10074
- }
10075
- return React.createElement(React.Fragment, null, fallback);
10076
- };
10077
- /**
10078
- * Higher-Order Component for permission checking
10079
- */
10080
- function withCan(action, subject, field) {
10081
- return function CanHOC(Component) {
10082
- const WrappedComponent = (props) => {
10083
- const { fallback, ...componentProps } = props;
10084
- return React.createElement(Can, { I: action, a: subject, field, fallback }, React.createElement(Component, componentProps));
10085
- };
10086
- WrappedComponent.displayName = `withCan(${Component.displayName || Component.name})`;
10087
- return WrappedComponent;
10088
- };
10089
- }
10090
- /**
10091
- * Higher-Order Component for permission denial checking
10092
- */
10093
- function withCannot(action, subject, field) {
10094
- return function CannotHOC(Component) {
10095
- const WrappedComponent = (props) => {
10096
- const { fallback, ...componentProps } = props;
10097
- return React.createElement(Cannot, { I: action, a: subject, field, fallback }, React.createElement(Component, componentProps));
10098
- };
10099
- WrappedComponent.displayName = `withCannot(${Component.displayName || Component.name})`;
10100
- return WrappedComponent;
10101
- };
10102
- }
10103
-
10104
- /**
10105
- * Fluent API for building permissions
10106
- */
10107
- class PermissionBuilder {
10108
- constructor() {
10109
- this.rules = [];
10110
- }
10111
- /**
10112
- * Grant permission
10113
- */
10114
- allow(action, subject, field) {
10115
- this.rules.push({
10116
- action,
10117
- subject: subject || '*',
10118
- fields: field ? [field] : undefined,
10119
- inverted: false
10120
- });
10121
- return this;
10122
- }
10123
- /**
10124
- * Deny permission
10125
- */
10126
- forbid(action, subject, field) {
10127
- this.rules.push({
10128
- action,
10129
- subject: subject || '*',
10130
- fields: field ? [field] : undefined,
10131
- inverted: true
10132
- });
10133
- return this;
10134
- }
10135
- /**
10136
- * Conditional permission
10137
- */
10138
- allowIf(action, subject, conditions, field) {
10139
- this.rules.push({
10140
- action,
10141
- subject: subject || '*',
10142
- fields: field ? [field] : undefined,
10143
- inverted: false,
10144
- conditions
10145
- });
10146
- return this;
10147
- }
10148
- /**
10149
- * Conditional denial
10150
- */
10151
- forbidIf(action, subject, conditions, field) {
10152
- this.rules.push({
10153
- action,
10154
- subject: subject || '*',
10155
- fields: field ? [field] : undefined,
10156
- inverted: true,
10157
- conditions
10158
- });
10159
- return this;
10160
- }
10161
- /**
10162
- * Grant all actions on a subject
10163
- */
10164
- manage(subject) {
10165
- this.rules.push({
10166
- action: '*',
10167
- subject,
10168
- inverted: false
10169
- });
10170
- return this;
10171
- }
10172
- /**
10173
- * Forbid all actions on a subject
10174
- */
10175
- forbidAll(subject) {
10176
- this.rules.push({
10177
- action: '*',
10178
- subject,
10179
- inverted: true
10180
- });
10181
- return this;
10182
- }
10183
- /**
10184
- * Role-based permissions
10185
- */
10186
- role(roleName, callback) {
10187
- const roleBuilder = new RoleBuilder(roleName);
10188
- callback(roleBuilder);
10189
- this.rules.push(...roleBuilder.getRules());
10190
- return this;
10191
- }
10192
- /**
10193
- * Build the ability with all rules
10194
- */
10195
- build() {
10196
- return new AbilityCore(this.rules);
10197
- }
10198
- /**
10199
- * Get all rules
10200
- */
10201
- getRules() {
10202
- return [...this.rules];
10203
- }
10204
- /**
10205
- * Clear all rules
10206
- */
10207
- clear() {
10208
- this.rules = [];
10209
- return this;
10210
- }
10211
- /**
10212
- * Merge rules from another builder
10213
- */
10214
- merge(other) {
10215
- this.rules.push(...other.getRules());
10216
- return this;
10217
- }
10218
- }
10219
- /**
10220
- * Role-specific permission builder
10221
- */
10222
- class RoleBuilder {
10223
- constructor(roleName) {
10224
- this.roleName = roleName;
10225
- this.rules = [];
10226
- }
10227
- /**
10228
- * Grant permission for this role
10229
- */
10230
- can(action, subject, field) {
10231
- this.rules.push({
10232
- action,
10233
- subject: subject || '*',
10234
- fields: field ? [field] : undefined,
10235
- inverted: false,
10236
- conditions: { role: this.roleName }
10237
- });
10238
- return this;
10239
- }
10240
- /**
10241
- * Deny permission for this role
10242
- */
10243
- cannot(action, subject, field) {
10244
- this.rules.push({
10245
- action,
10246
- subject: subject || '*',
10247
- fields: field ? [field] : undefined,
10248
- inverted: true,
10249
- conditions: { role: this.roleName }
10250
- });
10251
- return this;
10252
- }
10253
- /**
10254
- * Manage all actions on subject for this role
10255
- */
10256
- manage(subject) {
10257
- this.rules.push({
10258
- action: '*',
10259
- subject,
10260
- inverted: false,
10261
- conditions: { role: this.roleName }
10262
- });
10263
- return this;
10264
- }
10265
- /**
10266
- * Get all rules for this role
10267
- */
10268
- getRules() {
10269
- return [...this.rules];
10270
- }
10271
- }
10272
- /**
10273
- * Common permission patterns
10274
- */
10275
- class PermissionPatterns {
10276
- /**
10277
- * Admin permissions - can do everything
10278
- */
10279
- static admin() {
10280
- return new PermissionBuilder()
10281
- .manage('*');
10282
- }
10283
- /**
10284
- * User permissions - basic CRUD on own resources
10285
- */
10286
- static user(userId) {
10287
- return new PermissionBuilder()
10288
- .allowIf('read', 'User', { id: userId })
10289
- .allowIf('update', 'User', { id: userId })
10290
- .allowIf('delete', 'User', { id: userId })
10291
- .allow('read', 'public');
10292
- }
10293
- /**
10294
- * Guest permissions - read-only public content
10295
- */
10296
- static guest() {
10297
- return new PermissionBuilder()
10298
- .allow('read', 'public');
10299
- }
10300
- /**
10301
- * Moderator permissions - manage content but not users
10302
- */
10303
- static moderator() {
10304
- return new PermissionBuilder()
10305
- .manage('Content')
10306
- .manage('Comment')
10307
- .allow('read', 'User')
10308
- .forbid('delete', 'User');
10309
- }
10310
- /**
10311
- * Owner permissions - full control over owned resources
10312
- */
10313
- static owner(ownerId) {
10314
- return new PermissionBuilder()
10315
- .allowIf('*', '*', { ownerId })
10316
- .allow('create', '*');
10317
- }
10318
- }
10319
- /**
10320
- * Helper function to create a new permission builder
10321
- */
10322
- function permissions() {
10323
- return new PermissionBuilder();
10324
- }
10325
- /**
10326
- * Helper function to create ability from rules array
10327
- */
10328
- function defineAbility(callback) {
10329
- const builder = new PermissionBuilder();
10330
- callback(builder);
10331
- return builder.build();
10332
- }
10333
- /**
10334
- * Helper function to create common role-based abilities
10335
- */
10336
- function defineRoleAbility(role, callback) {
10337
- const roleBuilder = new RoleBuilder(role);
10338
- callback(roleBuilder);
10339
- return new AbilityCore(roleBuilder.getRules());
10340
- }
10341
-
10342
9778
  const ThemeModeContext = React.createContext(null);
10343
9779
  // Default persistence using localStorage (web only)
10344
9780
  const defaultPersistence = {
@@ -10468,7 +9904,6 @@ const OverlayBoundary = React.memo(function OverlayBoundary({ enabled, children
10468
9904
  const I18nBoundary = React.memo(function I18nBoundary({ locale, fallbackLocale, resources, children }) {
10469
9905
  return (jsxRuntime.jsx(I18nProvider, { initial: { locale, fallbackLocale, resources }, children: children }));
10470
9906
  });
10471
- const DEFAULT_PERMISSION_RULES = [{ action: '*', subject: '*' }];
10472
9907
  /**
10473
9908
  * Internal component that uses the enhanced theme mode when config is provided
10474
9909
  */
@@ -10506,30 +9941,20 @@ function PlatformBlocksContent({ children, theme, inherit = true, withCSSVariabl
10506
9941
  document.documentElement.setAttribute('data-platform-blocks-color-scheme', target);
10507
9942
  }
10508
9943
  }, [effectiveColorScheme, osColorScheme]);
10509
- const mainContent = (jsxRuntime.jsxs(ThemeBoundary, { theme: resolvedTheme, inherit: inherit, withCSSVariables: withCSSVariables, cssVariablesSelector: cssVariablesSelector, withGlobalCSS: withGlobalCSS, children: [children, withSpotlight && jsxRuntime.jsx(SpotlightController, { config: spotlightConfig })] }));
10510
- return (jsxRuntime.jsx(OverlayBoundary, { enabled: withOverlays, children: mainContent }));
9944
+ const mainContent = (jsxRuntime.jsx(ThemeBoundary, { theme: resolvedTheme, inherit: inherit, withCSSVariables: withCSSVariables, cssVariablesSelector: cssVariablesSelector, withGlobalCSS: withGlobalCSS, children: jsxRuntime.jsxs(OverlayBoundary, { enabled: withOverlays, children: [children, withSpotlight && jsxRuntime.jsx(SpotlightController, { config: spotlightConfig })] }) }));
9945
+ return mainContent;
10511
9946
  }
10512
9947
  /**
10513
9948
  * Main provider component for Platform Blocks library
10514
9949
  * Provides theme context and injects CSS variables
10515
9950
  */
10516
- 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 }) {
9951
+ 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 }) {
10517
9952
  const i18nStore = React.useMemo(() => i18nResources || { en: { translation: {} } }, [i18nResources]);
10518
9953
  const content = (jsxRuntime.jsx(PlatformBlocksContent, { theme: theme, inherit: inherit, withCSSVariables: withCSSVariables, cssVariablesSelector: cssVariablesSelector, colorSchemeMode: colorSchemeMode, withOverlays: withOverlays, withSpotlight: withSpotlight, withGlobalCSS: withGlobalCSS, spotlightConfig: spotlightConfig, themeModeConfig: themeModeConfig, children: children }));
10519
9954
  const themedTree = themeModeConfig ? (jsxRuntime.jsx(ThemeModeProvider, { config: themeModeConfig, children: content })) : (content);
10520
9955
  const directionConfig = direction === false ? null : (direction !== null && direction !== void 0 ? direction : {});
10521
9956
  const hapticsConfig = haptics === false ? null : (haptics !== null && haptics !== void 0 ? haptics : {});
10522
- const permissionConfig = permissions === false ? null : (() => {
10523
- const base = { ...(permissions !== null && permissions !== void 0 ? permissions : {}) };
10524
- if (base.rules === undefined) {
10525
- base.rules = DEFAULT_PERMISSION_RULES;
10526
- }
10527
- return base;
10528
- })();
10529
9957
  let enhancedTree = themedTree;
10530
- if (permissionConfig) {
10531
- enhancedTree = (jsxRuntime.jsx(PermissionProvider, { ...permissionConfig, children: enhancedTree }));
10532
- }
10533
9958
  if (hapticsConfig) {
10534
9959
  enhancedTree = (jsxRuntime.jsx(HapticsProvider, { ...hapticsConfig, children: enhancedTree }));
10535
9960
  }
@@ -11019,14 +10444,18 @@ function usePopoverPositioning(isOpen, options = {}) {
11019
10444
  // Get popover dimensions
11020
10445
  // Always use measureElement for robustness across platforms (RNW refs may not expose getBoundingClientRect)
11021
10446
  let popoverDimensions = { width: 200, height: 100 }; // sensible defaults for initial calculation
10447
+ let hasMeasuredPopover = false;
11022
10448
  if (popoverRef.current) {
11023
10449
  const popoverRect = await measureElement(popoverRef);
11024
10450
  if (popoverRect.width > 0 && popoverRect.height > 0) {
11025
10451
  popoverDimensions = { width: popoverRect.width, height: popoverRect.height };
10452
+ hasMeasuredPopover = true;
11026
10453
  }
11027
10454
  }
11028
10455
  // Calculate optimal position
11029
10456
  const result = calculateOverlayPositionEnhanced(anchorRect, popoverDimensions, positioningOptionsRef.current);
10457
+ // Mark whether this position is based on actual measurements
10458
+ result._hasMeasuredPopover = hasMeasuredPopover;
11030
10459
  setPosition(result);
11031
10460
  }
11032
10461
  catch (error) {
@@ -17277,6 +16706,7 @@ const TextInputBase = factory((props, ref) => {
17277
16706
  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;
17278
16707
  const renderDisclaimer = useDisclaimer(disclaimerData.disclaimer, disclaimerData.disclaimerProps);
17279
16708
  const [focused, setFocused] = React.useState(false);
16709
+ const [cursorVisible, setCursorVisible] = React.useState(true);
17280
16710
  const theme = useTheme();
17281
16711
  const { isRTL } = useDirection();
17282
16712
  const internalInputRef = React.useRef(null);
@@ -17348,6 +16778,16 @@ const TextInputBase = factory((props, ref) => {
17348
16778
  return '';
17349
16779
  return '•'.repeat(length);
17350
16780
  }, [isSecureEntry, normalizedValue]);
16781
+ // Blinking cursor for secure entry (simple interval toggle)
16782
+ React.useEffect(() => {
16783
+ if (focused && isSecureEntry) {
16784
+ setCursorVisible(true);
16785
+ const interval = setInterval(() => {
16786
+ setCursorVisible(v => !v);
16787
+ }, 530);
16788
+ return () => clearInterval(interval);
16789
+ }
16790
+ }, [focused, isSecureEntry]);
17351
16791
  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);
17352
16792
  const resolvedInputStyle = React.useMemo(() => {
17353
16793
  const base = [styles.input];
@@ -17449,7 +16889,12 @@ const TextInputBase = factory((props, ref) => {
17449
16889
  }
17450
16890
  }, [keyboardManager, pendingFocusTarget, focusTargetId]);
17451
16891
  const disclaimerNode = renderDisclaimer();
17452
- return (jsxRuntime.jsxs(reactNative.View, { style: [styles.container, spacingStyles, layoutStyles, style], ...rest, children: [jsxRuntime.jsx(FieldHeader, { label: label, description: description, required: required, withAsterisk: withAsterisk, disabled: disabled, error: !!error, size: size }), jsxRuntime.jsxs(reactNative.View, { style: styles.inputContainer, children: [startSection && (jsxRuntime.jsx(reactNative.View, { style: styles.startSection, children: startSection })), jsxRuntime.jsxs(reactNative.View, { style: { flex: 1, position: 'relative', justifyContent: 'center' }, children: [jsxRuntime.jsx(reactNative.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 && (jsxRuntime.jsx(reactNative.Text, { pointerEvents: "none", accessible: false, style: overlayStyle, numberOfLines: 1, ellipsizeMode: "clip", children: maskedValue }))] }), (showClearButton || endSection) && (jsxRuntime.jsxs(reactNative.View, { style: styles.endSection, children: [showClearButton && (jsxRuntime.jsx(ClearButton, { onPress: handleClear, size: size, accessibilityLabel: clearButtonLabelText, hasRightSection: !!endSection })), endSection] }))] }), disclaimerNode, error && (jsxRuntime.jsx(reactNative.Text, { style: styles.error, role: "alert", accessibilityLiveRegion: "polite", children: error })), helperText && !error && (jsxRuntime.jsx(reactNative.Text, { style: styles.helperText, children: helperText }))] }));
16892
+ return (jsxRuntime.jsxs(reactNative.View, { style: [styles.container, spacingStyles, layoutStyles, style], ...rest, children: [jsxRuntime.jsx(FieldHeader, { label: label, description: description, required: required, withAsterisk: withAsterisk, disabled: disabled, error: !!error, size: size }), jsxRuntime.jsxs(reactNative.View, { style: styles.inputContainer, children: [startSection && (jsxRuntime.jsx(reactNative.View, { style: styles.startSection, children: startSection })), jsxRuntime.jsxs(reactNative.View, { style: { flex: 1, position: 'relative', justifyContent: 'center' }, children: [jsxRuntime.jsx(reactNative.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 && (jsxRuntime.jsxs(reactNative.View, { pointerEvents: "none", style: [overlayStyle, { flexDirection: 'row', alignItems: 'center' }], children: [jsxRuntime.jsx(reactNative.Text, { accessible: false, style: { color: textColor, fontSize: styles.input.fontSize, fontFamily: theme.fontFamily }, numberOfLines: 1, children: maskedValue }), focused && cursorVisible && (jsxRuntime.jsx(reactNative.View, { style: {
16893
+ width: 1,
16894
+ height: styles.input.fontSize || 16,
16895
+ backgroundColor: textColor,
16896
+ marginLeft: 1,
16897
+ } }))] }))] }), (showClearButton || endSection) && (jsxRuntime.jsxs(reactNative.View, { style: styles.endSection, children: [showClearButton && (jsxRuntime.jsx(ClearButton, { onPress: handleClear, size: size, accessibilityLabel: clearButtonLabelText, hasRightSection: !!endSection })), endSection] }))] }), disclaimerNode, error && (jsxRuntime.jsx(reactNative.Text, { style: styles.error, role: "alert", accessibilityLiveRegion: "polite", children: error })), helperText && !error && (jsxRuntime.jsx(reactNative.Text, { style: styles.helperText, children: helperText }))] }));
17453
16898
  });
17454
16899
 
17455
16900
  const getInputTypeConfig = (type) => {
@@ -18007,18 +17452,19 @@ const useTextAreaStyles = (props) => {
18007
17452
  ? theme.colors.error[5]
18008
17453
  : styleProps.focused
18009
17454
  ? theme.colors.primary[5]
18010
- : styleProps.disabled
18011
- ? theme.backgrounds.border
18012
- : 'transparent',
17455
+ : theme.backgrounds.border,
18013
17456
  borderRadius: DESIGN_TOKENS.radius.lg,
18014
- borderWidth: DESIGN_TOKENS.radius.xs,
17457
+ borderWidth: 2,
18015
17458
  paddingHorizontal: DESIGN_TOKENS.spacing.sm,
18016
17459
  paddingVertical: DESIGN_TOKENS.spacing.xs,
17460
+ // Match Input focus treatment on web
18017
17461
  ...(styleProps.focused && !styleProps.disabled && reactNative.Platform.OS === 'web' && {
18018
17462
  boxShadow: `0 0 0 2px ${((_a = theme.states) === null || _a === void 0 ? void 0 : _a.focusRing) || theme.colors.primary[2]}`,
18019
17463
  }),
17464
+ // Light elevation similar to Input
18020
17465
  ...(!styleProps.disabled && theme.colorScheme === 'light' && {
18021
17466
  elevation: 1,
17467
+ boxShadow: '0 1px 2px rgba(0, 0, 0, 0.05)',
18022
17468
  }),
18023
17469
  opacity: styleProps.disabled ? DESIGN_TOKENS.opacity.disabled : 1,
18024
17470
  },
@@ -19700,7 +19146,7 @@ const useSwitchStyles = (props) => {
19700
19146
  ...(reactNative.Platform.OS === 'web' && { userSelect: 'none' }),
19701
19147
  },
19702
19148
  labelContainer: {
19703
- flex: 1,
19149
+ flexShrink: 1,
19704
19150
  justifyContent: 'center',
19705
19151
  },
19706
19152
  labelDisabled: {
@@ -19869,8 +19315,8 @@ const Switch = factory((rawProps, ref) => {
19869
19315
  const LayoutComponent = isVertical ? Column : Row;
19870
19316
  // For vertical layouts (top/bottom), we want tighter spacing and center alignment
19871
19317
  const layoutProps = isVertical
19872
- ? { gap: 'xs', style: { alignItems: 'center' } }
19873
- : { gap: 'sm', style: { alignItems: 'center' } };
19318
+ ? { gap: 'xs', align: 'center' }
19319
+ : { gap: 'sm', align: 'center' };
19874
19320
  const disclaimerNode = renderDisclaimer();
19875
19321
  return (jsxRuntime.jsxs(reactNative.View, { style: spacingStyles, children: [jsxRuntime.jsxs(LayoutComponent, { ...layoutProps, children: [labelPosition === 'top' && labelElement, labelPosition === 'left' && labelElement, switchElement, labelPosition === 'right' && labelElement, labelPosition === 'bottom' && labelElement] }), disclaimerNode ? (jsxRuntime.jsx(reactNative.View, { style: { width: '100%' }, children: disclaimerNode })) : null] }));
19876
19322
  });
@@ -27484,7 +26930,7 @@ const MiniCalendar = ({ value, onChange, defaultValue, numberOfDays = 7, default
27484
26930
  }
27485
26931
  onChange === null || onChange === void 0 ? void 0 : onChange(date);
27486
26932
  }, [isControlled, maxDate, minDate, onChange]);
27487
- return (jsxRuntime.jsxs(reactNative.View, { children: [jsxRuntime.jsxs(Flex, { direction: "row", justify: "space-between", align: "center", style: { marginBottom: 16 }, children: [jsxRuntime.jsx(reactNative.Pressable, { onPress: handlePrevious, style: ({ pressed }) => [
26933
+ return (jsxRuntime.jsxs(reactNative.View, { style: { alignSelf: 'flex-start' }, children: [jsxRuntime.jsxs(Flex, { direction: "row", justify: "space-between", align: "center", style: { marginBottom: 16 }, children: [jsxRuntime.jsx(reactNative.Pressable, { onPress: handlePrevious, style: ({ pressed }) => [
27488
26934
  {
27489
26935
  padding: 8,
27490
26936
  borderRadius: 6,
@@ -27496,7 +26942,7 @@ const MiniCalendar = ({ value, onChange, defaultValue, numberOfDays = 7, default
27496
26942
  borderRadius: 6,
27497
26943
  backgroundColor: pressed ? theme.colors.gray[2] : 'transparent',
27498
26944
  },
27499
- ], ...nextControlProps, children: jsxRuntime.jsx(Icon, { name: "chevron-right", size: 16, color: theme.colors.gray[6] }) })] }), jsxRuntime.jsx(reactNative.ScrollView, { horizontal: true, showsHorizontalScrollIndicator: false, children: jsxRuntime.jsx(Flex, { direction: "row", gap: 4, children: days.map((date) => {
26945
+ ], ...nextControlProps, children: jsxRuntime.jsx(Icon, { name: "chevron-right", size: 16, color: theme.colors.gray[6] }) })] }), jsxRuntime.jsx(reactNative.ScrollView, { horizontal: true, showsHorizontalScrollIndicator: false, contentContainerStyle: { flexGrow: 0 }, style: { flexGrow: 0 }, children: jsxRuntime.jsx(Flex, { direction: "row", gap: 4, children: days.map((date) => {
27500
26946
  const isSelected = selectedDate ? dateUtils$1.isSameDay(date, selectedDate) : false;
27501
26947
  const isToday = dateUtils$1.isToday(date);
27502
26948
  const isWeekend = dateUtils$1.isWeekend(date);
@@ -30237,6 +29683,7 @@ function MenuBase(props, ref) {
30237
29683
  const overlayId = openOverlay({
30238
29684
  content: menuDropdown,
30239
29685
  anchor: { x: positionResult.x, y: positionResult.y, width: overlaySize.width, height: overlaySize.height },
29686
+ placement: positionResult.placement || position,
30240
29687
  closeOnClickOutside,
30241
29688
  closeOnEscape,
30242
29689
  strategy,
@@ -32206,7 +31653,7 @@ function usePopoverContext(component) {
32206
31653
  const DEFAULT_ARROW_SIZE = 7;
32207
31654
  const PopoverBase = (props, ref) => {
32208
31655
  var _a;
32209
- const { children, opened: controlledOpened, defaultOpened = false, onChange, onOpen, onClose, onDismiss, disabled = false, closeOnClickOutside = true, closeOnEscape = true, clickOutsideEvents, // currently not implemented
31656
+ const { children, opened: controlledOpened, defaultOpened = false, onChange, onOpen, onClose, onDismiss, trigger = 'click', disabled = false, closeOnClickOutside = true, closeOnEscape = true, clickOutsideEvents, // currently not implemented
32210
31657
  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;
32211
31658
  const theme = useTheme();
32212
31659
  const { spacingProps } = extractSpacingProps(rest);
@@ -32218,9 +31665,18 @@ const PopoverBase = (props, ref) => {
32218
31665
  const openedRef = React.useRef(opened);
32219
31666
  const closingReasonRef = React.useRef(null);
32220
31667
  const anchorMeasurementsRef = React.useRef(null);
31668
+ const hoverTimeoutRef = React.useRef(null);
32221
31669
  React.useEffect(() => {
32222
31670
  openedRef.current = opened;
32223
31671
  }, [opened]);
31672
+ // Cleanup hover timeout on unmount
31673
+ React.useEffect(() => {
31674
+ return () => {
31675
+ if (hoverTimeoutRef.current) {
31676
+ clearTimeout(hoverTimeoutRef.current);
31677
+ }
31678
+ };
31679
+ }, []);
32224
31680
  const resolvedOffset = typeof offset === 'number' ? offset : (_a = offset === null || offset === void 0 ? void 0 : offset.mainAxis) !== null && _a !== void 0 ? _a : 8;
32225
31681
  const resolvedFlip = preventPositionChangeWhenVisible
32226
31682
  ? false
@@ -32233,7 +31689,7 @@ const PopoverBase = (props, ref) => {
32233
31689
  ? false
32234
31690
  : true;
32235
31691
  const resolvedStrategy = floatingStrategy !== null && floatingStrategy !== void 0 ? floatingStrategy : 'fixed';
32236
- const { position: positioningResult, anchorRef, popoverRef, showOverlay, hideOverlay, updatePosition } = useDropdownPositioning({
31692
+ const { position: positioningResult, anchorRef, popoverRef, showOverlay, hideOverlay, updatePosition, isPositioning } = useDropdownPositioning({
32237
31693
  isOpen: opened && !disabled && !!dropdownState,
32238
31694
  placement: position,
32239
31695
  offset: resolvedOffset,
@@ -32245,9 +31701,20 @@ const PopoverBase = (props, ref) => {
32245
31701
  fallbackPlacements,
32246
31702
  viewport,
32247
31703
  onClose: () => handleOverlayClose('dismiss'),
32248
- closeOnClickOutside,
31704
+ closeOnClickOutside: trigger === 'hover' ? false : closeOnClickOutside,
32249
31705
  closeOnEscape,
32250
31706
  });
31707
+ // Track if we've done measurement-based positioning to avoid flicker
31708
+ const hasPositionedRef = React.useRef(false);
31709
+ React.useEffect(() => {
31710
+ // Only mark as positioned when we have a measurement-based position
31711
+ if (opened && positioningResult && positioningResult._hasMeasuredPopover) {
31712
+ hasPositionedRef.current = true;
31713
+ }
31714
+ if (!opened) {
31715
+ hasPositionedRef.current = false;
31716
+ }
31717
+ }, [opened, positioningResult]);
32251
31718
  const popoverStyles = React.useMemo(() => createPopoverStyles(theme)({
32252
31719
  radius,
32253
31720
  shadow,
@@ -32351,6 +31818,28 @@ const PopoverBase = (props, ref) => {
32351
31818
  openPopover();
32352
31819
  }
32353
31820
  }, [closePopover, openPopover]);
31821
+ // Hover-specific handlers with delay to prevent glitching when moving between target and dropdown
31822
+ const handleHoverOpen = React.useCallback(() => {
31823
+ if (hoverTimeoutRef.current) {
31824
+ clearTimeout(hoverTimeoutRef.current);
31825
+ hoverTimeoutRef.current = null;
31826
+ }
31827
+ openPopover();
31828
+ }, [openPopover]);
31829
+ const handleHoverClose = React.useCallback(() => {
31830
+ if (hoverTimeoutRef.current) {
31831
+ clearTimeout(hoverTimeoutRef.current);
31832
+ }
31833
+ hoverTimeoutRef.current = setTimeout(() => {
31834
+ closePopover('programmatic');
31835
+ hoverTimeoutRef.current = null;
31836
+ }, 150); // Delay to allow mouse to move to dropdown
31837
+ }, [closePopover]);
31838
+ // Store hover handlers in refs to avoid causing re-renders in useEffect
31839
+ const hoverHandlersRef = React.useRef({ open: handleHoverOpen, close: handleHoverClose });
31840
+ React.useEffect(() => {
31841
+ hoverHandlersRef.current = { open: handleHoverOpen, close: handleHoverClose };
31842
+ }, [handleHoverOpen, handleHoverClose]);
32354
31843
  React.useEffect(() => {
32355
31844
  if (opened) {
32356
31845
  updateAnchorMeasurements();
@@ -32409,14 +31898,26 @@ const PopoverBase = (props, ref) => {
32409
31898
  if (typeof resolvedMaxHeight === 'number')
32410
31899
  sizeStyles.maxHeight = resolvedMaxHeight;
32411
31900
  const dropdownStyle = [popoverStyles.dropdown, dropdownState.style, sizeStyles];
32412
- const content = (jsxRuntime.jsxs(reactNative.View, { ref: popoverRef, style: [popoverStyles.wrapper, widthOverride ? { width: widthOverride } : null], pointerEvents: dropdownState.trapFocus ? 'auto' : 'box-none', testID: dropdownState.testID, onLayout: handleDropdownLayout, ...dropdownState.containerProps, children: [jsxRuntime.jsx(reactNative.View, { style: dropdownStyle, children: dropdownState.content }), withArrow && (jsxRuntime.jsx(reactNative.View, { style: getArrowStyle(positioningResult.placement, arrowSize, arrowRadius, arrowOffset, arrowPosition, theme) }))] }));
31901
+ // Hover handlers for the dropdown to keep it open when mouse moves from target to dropdown
31902
+ const dropdownHoverHandlers = trigger === 'hover' && reactNative.Platform.OS === 'web'
31903
+ ? {
31904
+ onMouseEnter: () => hoverHandlersRef.current.open(),
31905
+ onMouseLeave: () => hoverHandlersRef.current.close(),
31906
+ }
31907
+ : {};
31908
+ // Hide content until we have measurement-based positioning to prevent visual "snap"
31909
+ const hasMeasuredPosition = (positioningResult === null || positioningResult === void 0 ? void 0 : positioningResult._hasMeasuredPopover) === true;
31910
+ const visibilityStyle = !hasMeasuredPosition && reactNative.Platform.OS === 'web'
31911
+ ? { opacity: 0 }
31912
+ : {};
31913
+ const content = (jsxRuntime.jsxs(reactNative.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: [jsxRuntime.jsx(reactNative.View, { style: dropdownStyle, children: dropdownState.content }), withArrow && (jsxRuntime.jsx(reactNative.View, { style: getArrowStyle(positioningResult.placement, arrowSize, arrowRadius, arrowOffset, arrowPosition, theme) }))] }));
32413
31914
  showOverlay(content, {
32414
31915
  width: widthOverride,
32415
31916
  maxHeight: resolvedMaxHeight,
32416
31917
  zIndex,
32417
31918
  });
32418
31919
  onPositionChange === null || onPositionChange === void 0 ? void 0 : onPositionChange(positioningResult.placement);
32419
- }, [opened, dropdownState, positioningResult, popoverRef, showOverlay, hideOverlay, popoverStyles.dropdown, popoverStyles.wrapper, width, maxHeight, minWidth, minHeight, maxWidth, withArrow, arrowSize, arrowRadius, arrowOffset, arrowPosition, theme, zIndex, onPositionChange, schedulePositionUpdate]);
31920
+ }, [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]);
32420
31921
  React.useEffect(() => {
32421
31922
  return () => {
32422
31923
  hideOverlay();
@@ -32437,6 +31938,8 @@ const PopoverBase = (props, ref) => {
32437
31938
  open: openPopover,
32438
31939
  close: () => closePopover('programmatic'),
32439
31940
  toggle: togglePopover,
31941
+ hoverOpen: handleHoverOpen,
31942
+ hoverClose: handleHoverClose,
32440
31943
  registerDropdown,
32441
31944
  unregisterDropdown,
32442
31945
  anchorRef,
@@ -32445,7 +31948,8 @@ const PopoverBase = (props, ref) => {
32445
31948
  withRoles,
32446
31949
  disabled,
32447
31950
  returnFocus,
32448
- }), [opened, openPopover, closePopover, togglePopover, registerDropdown, unregisterDropdown, anchorRef, targetId, dropdownId, withRoles, disabled, returnFocus]);
31951
+ trigger,
31952
+ }), [opened, openPopover, closePopover, togglePopover, handleHoverOpen, handleHoverClose, registerDropdown, unregisterDropdown, anchorRef, targetId, dropdownId, withRoles, disabled, returnFocus, trigger]);
32449
31953
  const setContainerRef = React.useCallback((node) => {
32450
31954
  if (typeof ref === 'function') {
32451
31955
  ref(node);
@@ -32495,16 +31999,44 @@ const PopoverTargetBase = (props, ref) => {
32495
31999
  : { id: context.targetId };
32496
32000
  const composedRef = mergeRefs(children.ref, externalTargetRef);
32497
32001
  const triggerHandlers = {};
32498
- triggerHandlers.onPress = (...args) => {
32499
- const tgt = targetProps;
32500
- if (tgt && typeof tgt.onPress === 'function') {
32501
- tgt.onPress(...args);
32502
- }
32503
- if (typeof childProps.onPress === 'function') {
32504
- childProps.onPress(...args);
32505
- }
32506
- context.toggle();
32507
- };
32002
+ const wrapperHoverHandlers = {};
32003
+ // Click trigger: toggle on press
32004
+ if (context.trigger === 'click') {
32005
+ triggerHandlers.onPress = (...args) => {
32006
+ const tgt = targetProps;
32007
+ if (tgt && typeof tgt.onPress === 'function') {
32008
+ tgt.onPress(...args);
32009
+ }
32010
+ if (typeof childProps.onPress === 'function') {
32011
+ childProps.onPress(...args);
32012
+ }
32013
+ context.toggle();
32014
+ };
32015
+ }
32016
+ // Hover trigger: open/close on mouse enter/leave (web only)
32017
+ // Applied to the wrapper View for reliable hover detection
32018
+ if (context.trigger === 'hover' && reactNative.Platform.OS === 'web') {
32019
+ wrapperHoverHandlers.onMouseEnter = (...args) => {
32020
+ const tgt = targetProps;
32021
+ if (tgt && typeof tgt.onMouseEnter === 'function') {
32022
+ tgt.onMouseEnter(...args);
32023
+ }
32024
+ if (typeof childProps.onMouseEnter === 'function') {
32025
+ childProps.onMouseEnter(...args);
32026
+ }
32027
+ context.hoverOpen();
32028
+ };
32029
+ wrapperHoverHandlers.onMouseLeave = (...args) => {
32030
+ const tgt = targetProps;
32031
+ if (tgt && typeof tgt.onMouseLeave === 'function') {
32032
+ tgt.onMouseLeave(...args);
32033
+ }
32034
+ if (typeof childProps.onMouseLeave === 'function') {
32035
+ childProps.onMouseLeave(...args);
32036
+ }
32037
+ context.hoverClose();
32038
+ };
32039
+ }
32508
32040
  if (reactNative.Platform.OS === 'web') {
32509
32041
  triggerHandlers.onKeyDown = (event) => {
32510
32042
  const tgt = targetProps;
@@ -32526,7 +32058,14 @@ const PopoverTargetBase = (props, ref) => {
32526
32058
  };
32527
32059
  }
32528
32060
  const dynamicRefProp = { [refProp]: composedRef };
32529
- delete sanitizedTargetProps.onPress;
32061
+ // Remove handlers that we're overriding from sanitizedTargetProps
32062
+ if (context.trigger === 'click') {
32063
+ delete sanitizedTargetProps.onPress;
32064
+ }
32065
+ if (context.trigger === 'hover') {
32066
+ delete sanitizedTargetProps.onMouseEnter;
32067
+ delete sanitizedTargetProps.onMouseLeave;
32068
+ }
32530
32069
  delete sanitizedTargetProps.onKeyDown;
32531
32070
  const mergedProps = {
32532
32071
  ...sanitizedTargetProps,
@@ -32538,7 +32077,7 @@ const PopoverTargetBase = (props, ref) => {
32538
32077
  mergedProps.disabled = true;
32539
32078
  }
32540
32079
  const anchorWrapperRef = mergeRefs(context.anchorRef, ref);
32541
- return (jsxRuntime.jsx(reactNative.View, { ref: anchorWrapperRef, collapsable: false, children: React.cloneElement(children, mergedProps) }));
32080
+ return (jsxRuntime.jsx(reactNative.View, { ref: anchorWrapperRef, collapsable: false, ...wrapperHoverHandlers, children: React.cloneElement(children, mergedProps) }));
32542
32081
  };
32543
32082
  const PopoverDropdownBase = (props, _ref) => {
32544
32083
  const { children, trapFocus = false, keepMounted, style, testID, ...rest } = props;
@@ -32582,8 +32121,11 @@ function getArrowStyle(placement, arrowSize, arrowRadius, arrowOffset, arrowPosi
32582
32121
  const [side, alignment] = placement.split('-');
32583
32122
  switch (side) {
32584
32123
  case 'top':
32124
+ // Arrow points down, hide the borders that overlap with content (top-left corner after rotation)
32585
32125
  return {
32586
32126
  ...base,
32127
+ borderTopWidth: 0,
32128
+ borderLeftWidth: 0,
32587
32129
  bottom: -arrowSize,
32588
32130
  left: alignment === 'end'
32589
32131
  ? `calc(100% - ${(arrowPosition === 'side' ? arrowOffset : arrowSize)}px)`
@@ -32593,8 +32135,11 @@ function getArrowStyle(placement, arrowSize, arrowRadius, arrowOffset, arrowPosi
32593
32135
  marginLeft: alignment || arrowPosition === 'side' ? 0 : -arrowSize,
32594
32136
  };
32595
32137
  case 'bottom':
32138
+ // Arrow points up, hide the borders that overlap with content (bottom-right corner after rotation)
32596
32139
  return {
32597
32140
  ...base,
32141
+ borderBottomWidth: 0,
32142
+ borderRightWidth: 0,
32598
32143
  top: -arrowSize,
32599
32144
  left: alignment === 'end'
32600
32145
  ? `calc(100% - ${(arrowPosition === 'side' ? arrowOffset : arrowSize)}px)`
@@ -32604,8 +32149,11 @@ function getArrowStyle(placement, arrowSize, arrowRadius, arrowOffset, arrowPosi
32604
32149
  marginLeft: alignment || arrowPosition === 'side' ? 0 : -arrowSize,
32605
32150
  };
32606
32151
  case 'left':
32152
+ // Arrow points right, hide the borders that overlap with content (bottom-left corner after rotation)
32607
32153
  return {
32608
32154
  ...base,
32155
+ borderBottomWidth: 0,
32156
+ borderLeftWidth: 0,
32609
32157
  right: -arrowSize,
32610
32158
  top: alignment === 'end'
32611
32159
  ? `calc(100% - ${(arrowPosition === 'side' ? arrowOffset : arrowSize)}px)`
@@ -32615,8 +32163,11 @@ function getArrowStyle(placement, arrowSize, arrowRadius, arrowOffset, arrowPosi
32615
32163
  marginTop: alignment || arrowPosition === 'side' ? 0 : -arrowSize,
32616
32164
  };
32617
32165
  case 'right':
32166
+ // Arrow points left, hide the borders that overlap with content (top-right corner after rotation)
32618
32167
  return {
32619
32168
  ...base,
32169
+ borderTopWidth: 0,
32170
+ borderRightWidth: 0,
32620
32171
  left: -arrowSize,
32621
32172
  top: alignment === 'end'
32622
32173
  ? `calc(100% - ${(arrowPosition === 'side' ? arrowOffset : arrowSize)}px)`
@@ -35751,104 +35302,6 @@ const Ring = factory((props, ref) => {
35751
35302
  return (jsxRuntime.jsxs(reactNative.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: [jsxRuntime.jsxs(reactNative.View, { style: [styles$8.ringWrapper, { width: size, height: size }, ringStyle], children: [jsxRuntime.jsxs(Svg, { width: size, height: size, viewBox: `0 0 ${size} ${size}`, children: [jsxRuntime.jsx(Svg.Circle, { cx: size / 2, cy: size / 2, r: radius, stroke: defaultTrackColor, strokeWidth: thickness, fill: "transparent" }), jsxRuntime.jsx(Svg.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})` })] }), jsxRuntime.jsx(reactNative.View, { pointerEvents: "none", style: [styles$8.centerContent, { width: size, height: size }, contentStyle], children: centerContent })] }), caption !== undefined && caption !== null ? (React.isValidElement(caption) ? (caption) : (jsxRuntime.jsx(Text, { variant: "span", size: "xs", color: captionTextColor, weight: "600", style: [{ marginTop: 6, letterSpacing: 1 }, captionStyle], children: caption }))) : null] }));
35752
35303
  }, { displayName: 'Ring' });
35753
35304
 
35754
- const NAVIGATIONPROGRESS_DEFAULTS = {
35755
- size: 3,
35756
- color: 'primary',
35757
- zIndex: 9999,
35758
- overlay: true,
35759
- stepInterval: 500,
35760
- radius: 0,
35761
- };
35762
-
35763
- let subscribers = new Set();
35764
- let internalState = { value: 0, active: false };
35765
- let interval = null;
35766
- function broadcast() { subscribers.forEach(cb => cb({ ...internalState })); }
35767
- function schedule(intervalMs) {
35768
- clearInterval(interval);
35769
- interval = setInterval(() => {
35770
- if (!internalState.active)
35771
- return;
35772
- const remain = 100 - internalState.value;
35773
- const inc = Math.max(0.1, remain * 0.03);
35774
- internalState.value = Math.min(99, internalState.value + inc);
35775
- broadcast();
35776
- }, intervalMs);
35777
- }
35778
- const navigationProgress = {
35779
- start() { if (internalState.active)
35780
- return; internalState.active = true; if (internalState.value >= 100)
35781
- internalState.value = 0; broadcast(); schedule(NAVIGATIONPROGRESS_DEFAULTS.stepInterval); },
35782
- stop() { internalState.active = false; broadcast(); },
35783
- complete() { internalState.active = true; internalState.value = 100; broadcast(); setTimeout(() => { internalState.active = false; internalState.value = 0; broadcast(); }, 400); },
35784
- reset() { internalState.value = 0; internalState.active = false; broadcast(); },
35785
- set(v) { internalState.value = Math.max(0, Math.min(100, v)); broadcast(); },
35786
- increment(delta = 5) { internalState.value = Math.min(100, internalState.value + delta); broadcast(); },
35787
- decrement(delta = 5) { internalState.value = Math.max(0, internalState.value - delta); broadcast(); },
35788
- isActive() { return internalState.active; }
35789
- };
35790
- 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 }) => {
35791
- const theme = useTheme();
35792
- const scheme = useColorScheme();
35793
- const isDark = scheme === 'dark';
35794
- const progress = Animated.useSharedValue(0);
35795
- const opacity = Animated.useSharedValue(0);
35796
- React.useEffect(() => {
35797
- const sub = (s) => {
35798
- if (value == null) {
35799
- progress.value = Animated.withTiming(s.value, { duration: stepInterval });
35800
- opacity.value = Animated.withTiming(s.active ? 1 : 0, { duration: 150 });
35801
- }
35802
- };
35803
- subscribers.add(sub);
35804
- broadcast();
35805
- return () => { subscribers.delete(sub); };
35806
- }, [value, stepInterval, progress, opacity]);
35807
- React.useEffect(() => {
35808
- if (value != null) {
35809
- progress.value = Animated.withTiming(value, { duration: stepInterval });
35810
- opacity.value = Animated.withTiming(active ? 1 : 0, { duration: 150 });
35811
- }
35812
- }, [value, active, stepInterval]);
35813
- let resolvedColor = color;
35814
- if (theme.colors[color]) {
35815
- const bucket = theme.colors[color];
35816
- resolvedColor = bucket[5] || bucket[4] || bucket[0];
35817
- }
35818
- const barStyle = Animated.useAnimatedStyle(() => ({ width: `${progress.value}%` }));
35819
- const containerStyle = Animated.useAnimatedStyle(() => ({ opacity: opacity.value }));
35820
- return (jsxRuntime.jsxs(Animated.View, { style: [
35821
- {
35822
- position: overlay ? 'absolute' : 'relative',
35823
- top: 0,
35824
- left: 0,
35825
- right: 0,
35826
- height: size,
35827
- backgroundColor: isDark ? theme.colors.gray[3] : theme.colors.gray[2],
35828
- overflow: 'hidden',
35829
- zIndex,
35830
- borderRadius: radius,
35831
- pointerEvents: 'none'
35832
- },
35833
- containerStyle,
35834
- style
35835
- ], children: [jsxRuntime.jsx(Animated.View, { style: [{
35836
- position: 'absolute',
35837
- top: 0,
35838
- bottom: 0,
35839
- left: 0,
35840
- backgroundColor: resolvedColor,
35841
- borderRadius: radius,
35842
- }, barStyle] }), jsxRuntime.jsx(Animated.View, { style: [{
35843
- position: 'absolute',
35844
- top: 0,
35845
- bottom: 0,
35846
- right: 0,
35847
- width: 80,
35848
- backgroundColor: 'rgba(255,255,255,0.2)'
35849
- }, barStyle] })] }));
35850
- };
35851
-
35852
35305
  const DEFAULT_OPACITY = 0.6;
35853
35306
  const HEX_COLOR_REGEX = /^#?[0-9a-f]{3,8}$/i;
35854
35307
  const clampOpacity = (value) => {
@@ -36041,270 +35494,6 @@ const LoadingOverlay = React.forwardRef((props, ref) => {
36041
35494
  });
36042
35495
  LoadingOverlay.displayName = 'LoadingOverlay';
36043
35496
 
36044
- // A lightweight hover-activated floating panel similar to Mantine HoverCard
36045
- function HoverCardBase(props, ref) {
36046
- 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;
36047
- const [opened, setOpened] = React.useState(false);
36048
- const openTimeout = React.useRef(null);
36049
- const closeTimeout = React.useRef(null);
36050
- const containerRef = React.useRef(null);
36051
- const targetRef = React.useRef(null);
36052
- const overlayIdRef = React.useRef(null);
36053
- const overlayContentRef = React.useRef(null);
36054
- const isHoveringTargetRef = React.useRef(false);
36055
- const isHoveringOverlayRef = React.useRef(false);
36056
- const theme = useTheme();
36057
- const { openOverlay, closeOverlay, updateOverlay } = useOverlay();
36058
- const isOpened = controlledOpened !== undefined ? controlledOpened : opened;
36059
- const clearTimers = () => {
36060
- if (openTimeout.current) {
36061
- clearTimeout(openTimeout.current);
36062
- openTimeout.current = null;
36063
- }
36064
- if (closeTimeout.current) {
36065
- clearTimeout(closeTimeout.current);
36066
- closeTimeout.current = null;
36067
- }
36068
- };
36069
- const doOpen = React.useCallback(() => {
36070
- if (disabled)
36071
- return;
36072
- setOpened(true);
36073
- onOpen === null || onOpen === void 0 ? void 0 : onOpen();
36074
- }, [disabled, onOpen]);
36075
- const doClose = React.useCallback(() => {
36076
- setOpened(false);
36077
- onClose === null || onClose === void 0 ? void 0 : onClose();
36078
- }, [onClose]);
36079
- const scheduleOpen = React.useCallback(() => {
36080
- clearTimers();
36081
- openTimeout.current = setTimeout(doOpen, openDelay);
36082
- }, [doOpen, openDelay]);
36083
- const scheduleClose = React.useCallback(() => {
36084
- clearTimers();
36085
- closeTimeout.current = setTimeout(() => {
36086
- // Only close if neither target nor overlay are hovered (web)
36087
- if (reactNative.Platform.OS === 'web') {
36088
- if (isHoveringTargetRef.current || isHoveringOverlayRef.current)
36089
- return;
36090
- }
36091
- doClose();
36092
- }, closeDelay);
36093
- }, [doClose, closeDelay]);
36094
- React.useEffect(() => () => clearTimers(), []);
36095
- // Escape key (web only)
36096
- React.useEffect(() => {
36097
- if (!closeOnEscape || reactNative.Platform.OS !== 'web')
36098
- return;
36099
- const handler = (e) => { if (e.key === 'Escape')
36100
- doClose(); };
36101
- document.addEventListener('keydown', handler);
36102
- return () => document.removeEventListener('keydown', handler);
36103
- }, [closeOnEscape, doClose]);
36104
- const getInlinePositionStyle = () => {
36105
- const base = { position: 'absolute' };
36106
- switch (position) {
36107
- case 'top': return { ...base, bottom: '100%', left: 0, marginBottom: offset };
36108
- case 'bottom': return { ...base, top: '100%', left: 0, marginTop: offset };
36109
- case 'left': return { ...base, right: '100%', top: 0, marginRight: offset };
36110
- case 'right': return { ...base, left: '100%', top: 0, marginLeft: offset };
36111
- default: return { ...base, top: '100%', left: 0, marginTop: offset };
36112
- }
36113
- };
36114
- const shadowStyle = (() => {
36115
- switch (shadow) {
36116
- case 'sm':
36117
- return { boxShadow: '0 1px 2px rgba(0, 0, 0, 0.1)', elevation: 2 };
36118
- case 'md':
36119
- return { boxShadow: '0 2px 4px rgba(0, 0, 0, 0.15)', elevation: 4 };
36120
- case 'lg':
36121
- return { boxShadow: '0 4px 8px rgba(0, 0, 0, 0.2)', elevation: 8 };
36122
- default:
36123
- return {};
36124
- }
36125
- })();
36126
- const renderArrow = (placement) => {
36127
- if (!withArrow)
36128
- return null;
36129
- const base = { position: 'absolute', width: 0, height: 0 };
36130
- const color = theme.colors.gray[0];
36131
- const styles = {
36132
- top: { top: '100%', left: 12, borderLeftWidth: 6, borderRightWidth: 6, borderTopWidth: 6, borderLeftColor: 'transparent', borderRightColor: 'transparent', borderTopColor: color },
36133
- bottom: { bottom: '100%', left: 12, borderLeftWidth: 6, borderRightWidth: 6, borderBottomWidth: 6, borderLeftColor: 'transparent', borderRightColor: 'transparent', borderBottomColor: color },
36134
- left: { left: '100%', top: 12, borderTopWidth: 6, borderBottomWidth: 6, borderLeftWidth: 6, borderTopColor: 'transparent', borderBottomColor: 'transparent', borderLeftColor: color },
36135
- right: { right: '100%', top: 12, borderTopWidth: 6, borderBottomWidth: 6, borderRightWidth: 6, borderTopColor: 'transparent', borderBottomColor: 'transparent', borderRightColor: color },
36136
- };
36137
- const key = placement.split('-')[0];
36138
- return jsxRuntime.jsx(reactNative.View, { style: { ...base, ...(styles[key] || styles.top) } });
36139
- };
36140
- const openPortal = React.useCallback(async () => {
36141
- if (!withinPortal || !isOpened || overlayIdRef.current)
36142
- return;
36143
- const rect = await measureElement(targetRef);
36144
- const estWidth = width || 240;
36145
- const estHeight = 160; // rough initial height
36146
- const pos = calculateOverlayPositionEnhanced(rect, { width: estWidth, height: estHeight }, {
36147
- placement: position,
36148
- offset,
36149
- viewport: getViewport(),
36150
- strategy: 'fixed'
36151
- });
36152
- const overlayContent = (jsxRuntime.jsxs(reactNative.View, { ref: overlayContentRef, style: [
36153
- {
36154
- backgroundColor: theme.colors.gray[0],
36155
- borderRadius: getRadius$2(radius),
36156
- paddingHorizontal: getSpacing('md'),
36157
- paddingVertical: getSpacing('sm'),
36158
- borderWidth: 1,
36159
- borderColor: theme.colors.gray[3],
36160
- minWidth: width || 160,
36161
- maxWidth: width || 320,
36162
- },
36163
- shadowStyle,
36164
- ], ...(reactNative.Platform.OS === 'web' && trigger === 'hover' ? {
36165
- onMouseEnter: () => { isHoveringOverlayRef.current = true; clearTimers(); },
36166
- onMouseLeave: () => { isHoveringOverlayRef.current = false; scheduleClose(); },
36167
- } : {}), children: [children, renderArrow(pos.placement)] }));
36168
- const id = openOverlay({
36169
- content: overlayContent,
36170
- anchor: { x: pos.x, y: pos.y, width: estWidth, height: estHeight },
36171
- trigger: trigger,
36172
- // For hover-triggered overlays, do NOT render a click-outside backdrop – it steals hover
36173
- // and immediately fires target onMouseLeave. We rely on pointer leave timers instead.
36174
- closeOnClickOutside: trigger !== 'hover',
36175
- closeOnEscape: closeOnEscape,
36176
- strategy: 'fixed',
36177
- onClose: () => { overlayIdRef.current = null; if (opened)
36178
- setOpened(false); onClose === null || onClose === void 0 ? void 0 : onClose(); },
36179
- zIndex
36180
- });
36181
- overlayIdRef.current = id;
36182
- }, [withinPortal, isOpened, overlayIdRef, position, offset, width, trigger, closeOnEscape, theme, radius, shadowStyle, children, opened, onClose, getSpacing, getRadius$2]);
36183
- const closePortal = React.useCallback(() => {
36184
- if (overlayIdRef.current) {
36185
- closeOverlay(overlayIdRef.current);
36186
- overlayIdRef.current = null;
36187
- }
36188
- }, [closeOverlay]);
36189
- React.useEffect(() => {
36190
- if (withinPortal) {
36191
- if (isOpened)
36192
- openPortal();
36193
- else
36194
- closePortal();
36195
- }
36196
- return () => { if (!isOpened)
36197
- closePortal(); };
36198
- }, [isOpened, withinPortal, openPortal, closePortal]);
36199
- React.useEffect(() => {
36200
- if (!withinPortal || reactNative.Platform.OS !== 'web' || !isOpened || !overlayIdRef.current)
36201
- return;
36202
- const handler = () => {
36203
- Promise.all([measureElement(targetRef)]).then(([rect]) => {
36204
- var _a, _b;
36205
- const actualWidth = ((_a = overlayContentRef.current) === null || _a === void 0 ? void 0 : _a.offsetWidth) || width || 240;
36206
- const actualHeight = ((_b = overlayContentRef.current) === null || _b === void 0 ? void 0 : _b.offsetHeight) || 160;
36207
- const pos = calculateOverlayPositionEnhanced(rect, { width: actualWidth, height: actualHeight }, {
36208
- placement: position,
36209
- offset,
36210
- viewport: getViewport(),
36211
- strategy: 'fixed'
36212
- });
36213
- updateOverlay(overlayIdRef.current, { anchor: { x: pos.x, y: pos.y, width: actualWidth, height: actualHeight } });
36214
- });
36215
- };
36216
- window.addEventListener('resize', handler);
36217
- window.addEventListener('scroll', handler, true);
36218
- return () => {
36219
- window.removeEventListener('resize', handler);
36220
- window.removeEventListener('scroll', handler, true);
36221
- };
36222
- }, [withinPortal, isOpened, position, offset, width, updateOverlay]);
36223
- // Smart sizing: after content mounts, measure actual size and reposition if changed
36224
- React.useEffect(() => {
36225
- if (!withinPortal || !isOpened || !overlayIdRef.current)
36226
- return;
36227
- let frame;
36228
- const attempt = () => {
36229
- Promise.all([
36230
- measureElement(targetRef),
36231
- measureElement({ current: overlayContentRef.current })
36232
- ]).then(([targetRect, contentRect]) => {
36233
- if (!targetRect.width || !contentRect.width)
36234
- return; // skip invalid
36235
- const desiredWidth = width || contentRect.width;
36236
- const desiredHeight = contentRect.height;
36237
- // Recalculate with actual content size
36238
- const pos = calculateOverlayPositionEnhanced(targetRect, { width: desiredWidth, height: desiredHeight }, {
36239
- placement: position,
36240
- offset,
36241
- viewport: getViewport(),
36242
- strategy: 'fixed'
36243
- });
36244
- updateOverlay(overlayIdRef.current, { anchor: { x: pos.x, y: pos.y, width: desiredWidth, height: desiredHeight } });
36245
- });
36246
- };
36247
- // Delay a bit to allow layout
36248
- frame = setTimeout(attempt, 30);
36249
- return () => { if (frame)
36250
- clearTimeout(frame); };
36251
- }, [withinPortal, isOpened, position, offset, width, updateOverlay]);
36252
- const targetProps = {};
36253
- if (trigger === 'hover') {
36254
- if (reactNative.Platform.OS === 'web') {
36255
- targetProps.onMouseEnter = () => { isHoveringTargetRef.current = true; scheduleOpen(); };
36256
- targetProps.onMouseLeave = () => { isHoveringTargetRef.current = false; scheduleClose(); };
36257
- }
36258
- else {
36259
- // fallback: tap to toggle on native
36260
- targetProps.onPress = () => {
36261
- if (isOpened) {
36262
- doClose();
36263
- }
36264
- else {
36265
- doOpen();
36266
- }
36267
- };
36268
- }
36269
- }
36270
- else if (trigger === 'click') {
36271
- targetProps.onPress = () => {
36272
- if (isOpened) {
36273
- doClose();
36274
- }
36275
- else {
36276
- doOpen();
36277
- }
36278
- };
36279
- }
36280
- const inlineContent = (isOpened || keepMounted) && !withinPortal ? (jsxRuntime.jsxs(reactNative.View, { style: [
36281
- getInlinePositionStyle(),
36282
- {
36283
- backgroundColor: theme.colors.gray[0],
36284
- borderRadius: getRadius$2(radius),
36285
- paddingHorizontal: getSpacing('md'),
36286
- paddingVertical: getSpacing('sm'),
36287
- borderWidth: 1,
36288
- borderColor: theme.colors.gray[3],
36289
- minWidth: width,
36290
- zIndex,
36291
- },
36292
- shadowStyle,
36293
- ], pointerEvents: "auto", ...(reactNative.Platform.OS === 'web' && trigger === 'hover' ? {
36294
- onMouseEnter: () => { isHoveringOverlayRef.current = true; clearTimers(); },
36295
- onMouseLeave: () => { isHoveringOverlayRef.current = false; scheduleClose(); },
36296
- } : {}), children: [children, renderArrow(position)] })) : null;
36297
- return (jsxRuntime.jsxs(reactNative.View, { ref: ref, style: [{ position: 'relative', alignSelf: 'flex-start' }, style], testID: testID, children: [jsxRuntime.jsx(reactNative.Pressable, { ref: (node) => { containerRef.current = node; targetRef.current = node; }, ...targetProps, style: ({ pressed }) => {
36298
- var _a;
36299
- return [
36300
- { opacity: pressed ? 0.85 : 1 },
36301
- (_a = target === null || target === void 0 ? void 0 : target.props) === null || _a === void 0 ? void 0 : _a.style,
36302
- ];
36303
- }, children: target }), inlineContent] }));
36304
- }
36305
- const HoverCard = factory(HoverCardBase);
36306
- HoverCard.displayName = 'HoverCard';
36307
-
36308
35497
  const ContextMenu = ({ children, items, closeOnSelect = true, longPressDelay = 350, maxHeight = 280, onOpen, onClose, open: controlledOpen, position: controlledPosition, portalId, style, }) => {
36309
35498
  var _a, _b;
36310
35499
  const [internalOpen, setInternalOpen] = React.useState(false);
@@ -39894,11 +39083,14 @@ QRCodeSVG.displayName = 'QRCodeSVG';
39894
39083
  */
39895
39084
  function QRCode(props) {
39896
39085
  var _a;
39086
+ const theme = useTheme();
39897
39087
  const { spacingProps, otherProps: propsAfterSpacing } = extractSpacingProps(props);
39898
39088
  const { layoutProps, otherProps } = extractLayoutProps(propsAfterSpacing);
39899
- const { value, size = 400, backgroundColor = 'transparent', color = '#000000', errorCorrectionLevel = 'M', quietZone = 4, logo, style, testID, accessibilityLabel, onError, onLoadStart, // deprecated noop
39089
+ const { value, size = 400, backgroundColor = 'transparent', color, errorCorrectionLevel = 'M', quietZone = 4, logo, style, testID, accessibilityLabel, onError, onLoadStart, // deprecated noop
39900
39090
  onLoadEnd, // deprecated noop
39901
39091
  ...rest } = otherProps;
39092
+ // Default color to theme's primary text color for dark mode support
39093
+ const resolvedColor = color !== null && color !== void 0 ? color : theme.text.primary;
39902
39094
  const { copy } = useClipboard();
39903
39095
  const toast = useToast();
39904
39096
  const shouldCopyOnPress = !!otherProps.copyOnPress;
@@ -39914,7 +39106,7 @@ function QRCode(props) {
39914
39106
  });
39915
39107
  }
39916
39108
  }, [copy, copyValue, toast, otherProps.copyToastMessage, otherProps.copyToastTitle]);
39917
- const content = (jsxRuntime.jsxs(reactNative.View, { style: { borderRadius: 8, overflow: 'hidden' }, children: [jsxRuntime.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 && (jsxRuntime.jsx(CopyButton, { value: copyValue, iconOnly: true, size: "sm", style: { position: 'absolute', top: 8, right: 8 }, onCopy: () => { } }))] }));
39109
+ const content = (jsxRuntime.jsxs(reactNative.View, { style: { borderRadius: 8, overflow: 'hidden' }, children: [jsxRuntime.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 && (jsxRuntime.jsx(CopyButton, { value: copyValue, iconOnly: true, size: "sm", style: { position: 'absolute', top: 8, right: 8 }, onCopy: () => { } }))] }));
39918
39110
  if (shouldCopyOnPress) {
39919
39111
  return (jsxRuntime.jsx(reactNative.Pressable, { onPress: handleCopy, accessibilityLabel: accessibilityLabel || 'QR code', children: content }));
39920
39112
  }
@@ -41987,7 +41179,6 @@ function withPressAnimation(Component, animationProps) {
41987
41179
  */
41988
41180
  const AnimatedPressable = PressAnimation;
41989
41181
 
41990
- exports.AbilityCore = AbilityCore;
41991
41182
  exports.AccessibilityProvider = AccessibilityProvider;
41992
41183
  exports.Accordion = Accordion;
41993
41184
  exports.AmazonAppstoreBadge = AmazonAppstoreBadge;
@@ -42027,9 +41218,6 @@ exports.Button = Button;
42027
41218
  exports.COMPONENT_SIZES = COMPONENT_SIZES$1;
42028
41219
  exports.COMPONENT_SIZE_ORDER = COMPONENT_SIZE_ORDER;
42029
41220
  exports.Calendar = Calendar;
42030
- exports.Can = Can;
42031
- exports.CanWithConditions = CanWithConditions;
42032
- exports.Cannot = Cannot;
42033
41221
  exports.Card = Card;
42034
41222
  exports.Carousel = Carousel;
42035
41223
  exports.Checkbox = Checkbox;
@@ -42090,7 +41278,6 @@ exports.Heading4 = Heading4;
42090
41278
  exports.Heading5 = Heading5;
42091
41279
  exports.Heading6 = Heading6;
42092
41280
  exports.Highlight = Highlight;
42093
- exports.HoverCard = HoverCard;
42094
41281
  exports.HuaweiAppGalleryBadge = HuaweiAppGalleryBadge;
42095
41282
  exports.I18nProvider = I18nProvider;
42096
41283
  exports.Icon = Icon;
@@ -42128,7 +41315,6 @@ exports.MiniCalendar = MiniCalendar;
42128
41315
  exports.Month = Month;
42129
41316
  exports.MonthPicker = MonthPicker;
42130
41317
  exports.MonthPickerInput = MonthPickerInput;
42131
- exports.NavigationProgress = NavigationProgress;
42132
41318
  exports.Notice = Notice;
42133
41319
  exports.NumberInput = NumberInput;
42134
41320
  exports.Overlay = Overlay;
@@ -42136,10 +41322,6 @@ exports.OverlayProvider = OverlayProvider;
42136
41322
  exports.P = P;
42137
41323
  exports.Pagination = Pagination;
42138
41324
  exports.PasswordInput = PasswordInput;
42139
- exports.PermissionBuilder = PermissionBuilder;
42140
- exports.PermissionGate = PermissionGate;
42141
- exports.PermissionPatterns = PermissionPatterns;
42142
- exports.PermissionProvider = PermissionProvider;
42143
41325
  exports.PhoneInput = PhoneInput;
42144
41326
  exports.PinInput = PinInput;
42145
41327
  exports.PlatformBlocksProvider = PlatformBlocksProvider;
@@ -42154,7 +41336,6 @@ exports.Rating = Rating;
42154
41336
  exports.RedditJoinBadge = RedditJoinBadge;
42155
41337
  exports.RichTextEditor = RichTextEditor;
42156
41338
  exports.Ring = Ring;
42157
- exports.RoleBuilder = RoleBuilder;
42158
41339
  exports.Row = Row;
42159
41340
  exports.SIZE_SCALES = SIZE_SCALES;
42160
41341
  exports.Search = Search;
@@ -42212,9 +41393,7 @@ exports.createSound = createSound;
42212
41393
  exports.createSpotlightStore = createSpotlightStore;
42213
41394
  exports.createTheme = createTheme;
42214
41395
  exports.debounce = debounce$1;
42215
- exports.defineAbility = defineAbility;
42216
41396
  exports.defineAppLayout = defineAppLayout;
42217
- exports.defineRoleAbility = defineRoleAbility;
42218
41397
  exports.directSpotlight = directSpotlight;
42219
41398
  exports.extractDisclaimerProps = extractDisclaimerProps;
42220
41399
  exports.factory = factory;
@@ -42235,11 +41414,9 @@ exports.globalHotkeys = globalHotkeys;
42235
41414
  exports.measureAsyncPerformance = measureAsyncPerformance;
42236
41415
  exports.measureElement = measureElement;
42237
41416
  exports.measurePerformance = measurePerformance;
42238
- exports.navigationProgress = navigationProgress;
42239
41417
  exports.onDialogsRequested = onDialogsRequested;
42240
41418
  exports.onSpotlightRequested = onSpotlightRequested;
42241
41419
  exports.onToastsRequested = onToastsRequested;
42242
- exports.permissions = permissions;
42243
41420
  exports.pointInRect = pointInRect;
42244
41421
  exports.polymorphicFactory = polymorphicFactory;
42245
41422
  exports.px = px;
@@ -42250,7 +41427,6 @@ exports.resolveResponsiveValue = resolveResponsiveValue;
42250
41427
  exports.resolveSize = resolveSize;
42251
41428
  exports.spotlight = spotlight;
42252
41429
  exports.throttle = throttle;
42253
- exports.useAbility = useAbility;
42254
41430
  exports.useAccessibility = useAccessibility;
42255
41431
  exports.useAppLayoutContext = useAppLayoutContext;
42256
41432
  exports.useAppShell = useAppShell;
@@ -42280,7 +41456,6 @@ exports.useOptionalFormContext = useOptionalFormContext;
42280
41456
  exports.useOverlay = useOverlay;
42281
41457
  exports.useOverlayApi = useOverlayApi;
42282
41458
  exports.useOverlays = useOverlays;
42283
- exports.usePermissions = usePermissions;
42284
41459
  exports.usePopoverPositioning = usePopoverPositioning;
42285
41460
  exports.useSimpleDialog = useSimpleDialog;
42286
41461
  exports.useSound = useSound;
@@ -42295,8 +41470,6 @@ exports.useToast = useToast;
42295
41470
  exports.useToastApi = useToastApi;
42296
41471
  exports.useToggleColorScheme = useToggleColorScheme;
42297
41472
  exports.useTooltipPositioning = useTooltipPositioning;
42298
- exports.withCan = withCan;
42299
- exports.withCannot = withCannot;
42300
41473
  exports.withDisclaimer = withDisclaimer;
42301
41474
  exports.withPressAnimation = withPressAnimation;
42302
41475
  //# sourceMappingURL=index.js.map