@khanacademy/wonder-blocks-core 12.4.3 → 12.5.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/CHANGELOG.md +13 -0
- package/dist/es/index.js +5 -3
- package/dist/hooks/use-direction-detection.d.ts +58 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +5 -2
- package/dist/util/types.d.ts +8 -0
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
# @khanacademy/wonder-blocks-core
|
|
2
2
|
|
|
3
|
+
## 12.5.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 63dcc94: Move `useDirectionDetection` from `wonder-blocks-modal` (internal-only) to `wonder-blocks-core` and export it publicly, so other packages can detect the nearest ancestor's writing direction (RTL/LTR) without depending on `wonder-blocks-modal`. `wonder-blocks-modal`'s own usage (`DrawerDialog`) now imports it from `wonder-blocks-core`; no change to `wonder-blocks-modal`'s public API.
|
|
8
|
+
|
|
9
|
+
## 12.4.4
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- 40cb70f: Add `require-logical-properties-for-rtl` ESLint rule to `eslint-plugin-wonder-blocks` recommended config, and migrate all Wonder Blocks component source files to use CSS logical properties for improved RTL layout support.
|
|
14
|
+
- 40cb70f: Enable RTL logical-properties ESLint rule in recommended config
|
|
15
|
+
|
|
3
16
|
## 12.4.3
|
|
4
17
|
|
|
5
18
|
### Patch Changes
|
package/dist/es/index.js
CHANGED
|
@@ -5,11 +5,11 @@ import { StyleSheet, css } from 'aphrodite';
|
|
|
5
5
|
|
|
6
6
|
function flatten(list){const result=[];if(!list){return result}else if(Array.isArray(list)){for(const item of list){result.push(...flatten(item));}}else {result.push(list);}return result}function processStyleList(style){const stylesheetStyles=[];const inlineStyles=[];if(!style){return {style:{},className:""}}const shouldInlineStyles=typeof global!=="undefined"&&global.SNAPSHOT_INLINE_APHRODITE;flatten(style).forEach(child=>{const _definition=child._definition;if(_definition!=null){if(shouldInlineStyles){const def={};for(const[key,value]of Object.entries(_definition)){def[key.replace(/-[a-z]/g,match=>match[1].toUpperCase())]=value;}inlineStyles.push(def);}else {stylesheetStyles.push(child);}}else {inlineStyles.push(child);}});const inlineStylesObject=Object.assign({},...inlineStyles);if(inlineStyles.length>0&&!shouldInlineStyles){const inlineStylesStyleSheet=StyleSheet.create({inlineStyles:inlineStylesObject});stylesheetStyles.push(inlineStylesStyleSheet.inlineStyles);}return {style:shouldInlineStyles?inlineStylesObject:{},className:css(...stylesheetStyles)}}
|
|
7
7
|
|
|
8
|
-
const isHeaderRegex=/^h[1-6]$/;const styles$1=StyleSheet.create({text:{WebkitFontSmoothing:"antialiased",MozOsxFontSmoothing:"grayscale"},header:{
|
|
8
|
+
const isHeaderRegex=/^h[1-6]$/;const styles$1=StyleSheet.create({text:{WebkitFontSmoothing:"antialiased",MozOsxFontSmoothing:"grayscale"},header:{marginBlockStart:0,marginBlockEnd:0}});const Text=React.forwardRef(function Text({children,style,tag="span",testId,...otherProps},ref){const Tag=tag;const isHeader=isHeaderRegex.test(tag);const styleAttributes=processStyleList([styles$1.text,isHeader&&styles$1.header,style]);const classNames=otherProps.className?[otherProps.className,styleAttributes.className].join(" "):styleAttributes.className;return jsx(Tag,{...otherProps,style:styleAttributes.style,className:classNames,"data-testid":testId,ref:ref,children:children})});
|
|
9
9
|
|
|
10
10
|
function addStyle(Component,defaultStyle){return React.forwardRef((props,ref)=>{const{className,style,...otherProps}=props;const reset=typeof Component==="string"?overrides[Component]:null;const{className:aphroditeClassName,style:inlineStyles}=processStyleList([reset,defaultStyle,style]);return jsx(Component,{...otherProps,ref:ref,className:[aphroditeClassName,className].filter(Boolean).join(" "),style:inlineStyles})})}const overrides=StyleSheet.create({button:{margin:0,"::-moz-focus-inner":{border:0}}});
|
|
11
11
|
|
|
12
|
-
const styles=StyleSheet.create({default:{alignItems:"stretch",borderWidth:0,borderStyle:"solid",boxSizing:"border-box",display:"flex",flexDirection:"column",margin:0,padding:0,position:"relative",zIndex:0,
|
|
12
|
+
const styles=StyleSheet.create({default:{alignItems:"stretch",borderWidth:0,borderStyle:"solid",boxSizing:"border-box",display:"flex",flexDirection:"column",margin:0,padding:0,position:"relative",zIndex:0,minBlockSize:0,minInlineSize:0}});const View=React.forwardRef(function View(props,ref){const{testId,tag="div",...restProps}=props;const commonProps={...restProps,"data-testid":testId};const StyledTag=useMemo(()=>addStyle(tag,styles.default),[tag]);return jsx(StyledTag,{...commonProps,ref:ref})});
|
|
13
13
|
|
|
14
14
|
var RenderState=/*#__PURE__*/function(RenderState){RenderState["Initial"]="initial";RenderState["Standard"]="standard";return RenderState}({});var RenderStateInternal=/*#__PURE__*/function(RenderStateInternal){RenderStateInternal["Root"]="root";RenderStateInternal["Initial"]="initial";RenderStateInternal["Standard"]="standard";return RenderStateInternal}({});const RenderStateContext=React.createContext("root");RenderStateContext.displayName="RenderStateContext";
|
|
15
15
|
|
|
@@ -19,6 +19,8 @@ let serverSide=false;var Server = {isServerSide:()=>serverSide,setServerSide:()=
|
|
|
19
19
|
|
|
20
20
|
const Id=({id,children})=>{const generatedId=useId();return jsx(Fragment,{children:children(id??generatedId)})};
|
|
21
21
|
|
|
22
|
+
function useDirectionDetection(elementRef,options={}){const{direction:explicitDirection,defaultDirection="ltr"}=options;if(explicitDirection){return explicitDirection}if(elementRef?.current){const elementWithDir=elementRef.current.closest("[dir]");if(elementWithDir){const dirValue=elementWithDir.getAttribute("dir");return dirValue==="rtl"||dirValue==="ltr"?dirValue:defaultDirection}}const documentDir=document.documentElement.getAttribute("dir")||document.body.getAttribute("dir");if(documentDir==="rtl"||documentDir==="ltr"){return documentDir}return defaultDirection}
|
|
23
|
+
|
|
22
24
|
const useForceUpdate=()=>{const[,setUpdateState]=React.useState({});const forceUpdate=React.useCallback(()=>{setUpdateState({});},[]);return forceUpdate};
|
|
23
25
|
|
|
24
26
|
const useIsMounted=()=>{const isMountedRef=React.useRef(false);const isMounted=React.useCallback(()=>isMountedRef.current,[]);React.useEffect(()=>{isMountedRef.current=true;return ()=>{isMountedRef.current=false;}},[]);return isMounted};
|
|
@@ -39,4 +41,4 @@ const keys={enter:"Enter",escape:"Escape",tab:"Tab",space:" ",up:"ArrowUp",down:
|
|
|
39
41
|
|
|
40
42
|
const FOCUSABLE_ELEMENTS='button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])';function findFocusableNodes(root){return Array.from(root.querySelectorAll(FOCUSABLE_ELEMENTS))}
|
|
41
43
|
|
|
42
|
-
export { Id, InitialFallback, RenderState, RenderStateRoot, Server, Text, View, addStyle, findFocusableNodes, keys, useForceUpdate, useIsMounted, useLatestRef, useOnMountEffect, useOnline, usePreHydrationEffect, useRenderState };
|
|
44
|
+
export { Id, InitialFallback, RenderState, RenderStateRoot, Server, Text, View, addStyle, findFocusableNodes, keys, useDirectionDetection, useForceUpdate, useIsMounted, useLatestRef, useOnMountEffect, useOnline, usePreHydrationEffect, useRenderState };
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
type Direction = "ltr" | "rtl";
|
|
3
|
+
interface DirectionDetectionOptions {
|
|
4
|
+
/**
|
|
5
|
+
* Explicit direction to use, bypassing DOM detection.
|
|
6
|
+
* Useful when direction is known from external sources like RequestInfo.
|
|
7
|
+
*/
|
|
8
|
+
direction?: Direction;
|
|
9
|
+
/**
|
|
10
|
+
* The default direction to use if no dir attribute is found.
|
|
11
|
+
* Defaults to "ltr".
|
|
12
|
+
*/
|
|
13
|
+
defaultDirection?: Direction;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Hook for detecting the text direction (LTR/RTL). Supports both element-specific
|
|
17
|
+
* and document-level direction detection.
|
|
18
|
+
*
|
|
19
|
+
* This hook performs DOM queries on each render and does not use state or
|
|
20
|
+
* observers, making it stable and predictable.
|
|
21
|
+
*
|
|
22
|
+
* @param elementRef - Optional ref to the element to start searching from.
|
|
23
|
+
* If provided, searches up the DOM tree from this element.
|
|
24
|
+
* If not provided, uses document-level detection.
|
|
25
|
+
* @param options - Configuration options for direction detection
|
|
26
|
+
* @returns The detected direction ("ltr" or "rtl")
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* ```tsx
|
|
30
|
+
* // Element-specific detection - searches up DOM tree from the element
|
|
31
|
+
* const MyComponent = () => {
|
|
32
|
+
* const ref = React.useRef<HTMLDivElement>(null);
|
|
33
|
+
* const direction = useDirectionDetection(ref);
|
|
34
|
+
*
|
|
35
|
+
* return (
|
|
36
|
+
* <div ref={ref}>
|
|
37
|
+
* Direction is: {direction}
|
|
38
|
+
* </div>
|
|
39
|
+
* );
|
|
40
|
+
* };
|
|
41
|
+
*
|
|
42
|
+
* // Document-level detection - uses document.documentElement.dir
|
|
43
|
+
* const MyOtherComponent = () => {
|
|
44
|
+
* const direction = useDirectionDetection();
|
|
45
|
+
* return <div>Page direction is: {direction}</div>;
|
|
46
|
+
* };
|
|
47
|
+
*
|
|
48
|
+
* // With explicit direction (e.g., from RequestInfo)
|
|
49
|
+
* const MyRTLComponent = ({requestInfo}) => {
|
|
50
|
+
* const direction = useDirectionDetection(undefined, {
|
|
51
|
+
* direction: requestInfo.isRTL ? "rtl" : "ltr"
|
|
52
|
+
* });
|
|
53
|
+
* return <div>Explicit direction: {direction}</div>;
|
|
54
|
+
* };
|
|
55
|
+
* ```
|
|
56
|
+
*/
|
|
57
|
+
export declare function useDirectionDetection(elementRef?: React.RefObject<HTMLElement | null>, options?: DirectionDetectionOptions): Direction;
|
|
58
|
+
export {};
|
package/dist/index.d.ts
CHANGED
|
@@ -4,6 +4,7 @@ export { default as InitialFallback } from "./components/initial-fallback";
|
|
|
4
4
|
export { default as addStyle } from "./util/add-style";
|
|
5
5
|
export { default as Server } from "./util/server";
|
|
6
6
|
export { Id } from "./components/id";
|
|
7
|
+
export { useDirectionDetection } from "./hooks/use-direction-detection";
|
|
7
8
|
export { useForceUpdate } from "./hooks/use-force-update";
|
|
8
9
|
export { useIsMounted } from "./hooks/use-is-mounted";
|
|
9
10
|
export { useLatestRef } from "./hooks/use-latest-ref";
|
package/dist/index.js
CHANGED
|
@@ -28,11 +28,11 @@ var React__namespace = /*#__PURE__*/_interopNamespace(React);
|
|
|
28
28
|
|
|
29
29
|
function flatten(list){const result=[];if(!list){return result}else if(Array.isArray(list)){for(const item of list){result.push(...flatten(item));}}else {result.push(list);}return result}function processStyleList(style){const stylesheetStyles=[];const inlineStyles=[];if(!style){return {style:{},className:""}}const shouldInlineStyles=typeof global!=="undefined"&&global.SNAPSHOT_INLINE_APHRODITE;flatten(style).forEach(child=>{const _definition=child._definition;if(_definition!=null){if(shouldInlineStyles){const def={};for(const[key,value]of Object.entries(_definition)){def[key.replace(/-[a-z]/g,match=>match[1].toUpperCase())]=value;}inlineStyles.push(def);}else {stylesheetStyles.push(child);}}else {inlineStyles.push(child);}});const inlineStylesObject=Object.assign({},...inlineStyles);if(inlineStyles.length>0&&!shouldInlineStyles){const inlineStylesStyleSheet=aphrodite.StyleSheet.create({inlineStyles:inlineStylesObject});stylesheetStyles.push(inlineStylesStyleSheet.inlineStyles);}return {style:shouldInlineStyles?inlineStylesObject:{},className:aphrodite.css(...stylesheetStyles)}}
|
|
30
30
|
|
|
31
|
-
const isHeaderRegex=/^h[1-6]$/;const styles$1=aphrodite.StyleSheet.create({text:{WebkitFontSmoothing:"antialiased",MozOsxFontSmoothing:"grayscale"},header:{
|
|
31
|
+
const isHeaderRegex=/^h[1-6]$/;const styles$1=aphrodite.StyleSheet.create({text:{WebkitFontSmoothing:"antialiased",MozOsxFontSmoothing:"grayscale"},header:{marginBlockStart:0,marginBlockEnd:0}});const Text=React__namespace.forwardRef(function Text({children,style,tag="span",testId,...otherProps},ref){const Tag=tag;const isHeader=isHeaderRegex.test(tag);const styleAttributes=processStyleList([styles$1.text,isHeader&&styles$1.header,style]);const classNames=otherProps.className?[otherProps.className,styleAttributes.className].join(" "):styleAttributes.className;return jsxRuntime.jsx(Tag,{...otherProps,style:styleAttributes.style,className:classNames,"data-testid":testId,ref:ref,children:children})});
|
|
32
32
|
|
|
33
33
|
function addStyle(Component,defaultStyle){return React__namespace.forwardRef((props,ref)=>{const{className,style,...otherProps}=props;const reset=typeof Component==="string"?overrides[Component]:null;const{className:aphroditeClassName,style:inlineStyles}=processStyleList([reset,defaultStyle,style]);return jsxRuntime.jsx(Component,{...otherProps,ref:ref,className:[aphroditeClassName,className].filter(Boolean).join(" "),style:inlineStyles})})}const overrides=aphrodite.StyleSheet.create({button:{margin:0,"::-moz-focus-inner":{border:0}}});
|
|
34
34
|
|
|
35
|
-
const styles=aphrodite.StyleSheet.create({default:{alignItems:"stretch",borderWidth:0,borderStyle:"solid",boxSizing:"border-box",display:"flex",flexDirection:"column",margin:0,padding:0,position:"relative",zIndex:0,
|
|
35
|
+
const styles=aphrodite.StyleSheet.create({default:{alignItems:"stretch",borderWidth:0,borderStyle:"solid",boxSizing:"border-box",display:"flex",flexDirection:"column",margin:0,padding:0,position:"relative",zIndex:0,minBlockSize:0,minInlineSize:0}});const View=React__namespace.forwardRef(function View(props,ref){const{testId,tag="div",...restProps}=props;const commonProps={...restProps,"data-testid":testId};const StyledTag=React.useMemo(()=>addStyle(tag,styles.default),[tag]);return jsxRuntime.jsx(StyledTag,{...commonProps,ref:ref})});
|
|
36
36
|
|
|
37
37
|
var RenderState=/*#__PURE__*/function(RenderState){RenderState["Initial"]="initial";RenderState["Standard"]="standard";return RenderState}({});var RenderStateInternal=/*#__PURE__*/function(RenderStateInternal){RenderStateInternal["Root"]="root";RenderStateInternal["Initial"]="initial";RenderStateInternal["Standard"]="standard";return RenderStateInternal}({});const RenderStateContext=React__namespace.createContext("root");RenderStateContext.displayName="RenderStateContext";
|
|
38
38
|
|
|
@@ -42,6 +42,8 @@ let serverSide=false;var Server = {isServerSide:()=>serverSide,setServerSide:()=
|
|
|
42
42
|
|
|
43
43
|
const Id=({id,children})=>{const generatedId=React.useId();return jsxRuntime.jsx(jsxRuntime.Fragment,{children:children(id??generatedId)})};
|
|
44
44
|
|
|
45
|
+
function useDirectionDetection(elementRef,options={}){const{direction:explicitDirection,defaultDirection="ltr"}=options;if(explicitDirection){return explicitDirection}if(elementRef?.current){const elementWithDir=elementRef.current.closest("[dir]");if(elementWithDir){const dirValue=elementWithDir.getAttribute("dir");return dirValue==="rtl"||dirValue==="ltr"?dirValue:defaultDirection}}const documentDir=document.documentElement.getAttribute("dir")||document.body.getAttribute("dir");if(documentDir==="rtl"||documentDir==="ltr"){return documentDir}return defaultDirection}
|
|
46
|
+
|
|
45
47
|
const useForceUpdate=()=>{const[,setUpdateState]=React__namespace.useState({});const forceUpdate=React__namespace.useCallback(()=>{setUpdateState({});},[]);return forceUpdate};
|
|
46
48
|
|
|
47
49
|
const useIsMounted=()=>{const isMountedRef=React__namespace.useRef(false);const isMounted=React__namespace.useCallback(()=>isMountedRef.current,[]);React__namespace.useEffect(()=>{isMountedRef.current=true;return ()=>{isMountedRef.current=false;}},[]);return isMounted};
|
|
@@ -72,6 +74,7 @@ exports.View = View;
|
|
|
72
74
|
exports.addStyle = addStyle;
|
|
73
75
|
exports.findFocusableNodes = findFocusableNodes;
|
|
74
76
|
exports.keys = keys;
|
|
77
|
+
exports.useDirectionDetection = useDirectionDetection;
|
|
75
78
|
exports.useForceUpdate = useForceUpdate;
|
|
76
79
|
exports.useIsMounted = useIsMounted;
|
|
77
80
|
exports.useLatestRef = useLatestRef;
|
package/dist/util/types.d.ts
CHANGED
|
@@ -79,7 +79,15 @@ export type TextViewSharedProps = {
|
|
|
79
79
|
tabIndex?: number;
|
|
80
80
|
id?: string;
|
|
81
81
|
title?: string;
|
|
82
|
+
/**
|
|
83
|
+
* Should be ignored
|
|
84
|
+
* @ignore
|
|
85
|
+
*/
|
|
82
86
|
"data-modal-launcher-portal"?: boolean;
|
|
87
|
+
/**
|
|
88
|
+
* Should be ignored
|
|
89
|
+
* @ignore
|
|
90
|
+
*/
|
|
83
91
|
"data-placement"?: string;
|
|
84
92
|
} & AriaProps & EventHandlers;
|
|
85
93
|
export * from "./types.propsfor";
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"description": "",
|
|
4
4
|
"author": "Khan Academy",
|
|
5
5
|
"license": "MIT",
|
|
6
|
-
"version": "12.
|
|
6
|
+
"version": "12.5.0",
|
|
7
7
|
"publishConfig": {
|
|
8
8
|
"access": "public"
|
|
9
9
|
},
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
},
|
|
31
31
|
"devDependencies": {
|
|
32
32
|
"@khanacademy/wb-dev-build-settings": "3.3.0",
|
|
33
|
-
"@khanacademy/wonder-blocks-testing-core": "5.
|
|
33
|
+
"@khanacademy/wonder-blocks-testing-core": "5.2.0"
|
|
34
34
|
},
|
|
35
35
|
"scripts": {
|
|
36
36
|
"test": "echo \"Error: no test specified\" && exit 1"
|