@elliemae/ds-read-more 3.57.0-next.5 → 3.57.0-next.50
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/DSReadMore.js +6 -2
- package/dist/cjs/DSReadMore.js.map +2 -2
- package/dist/cjs/MoreLessButton.js +25 -17
- package/dist/cjs/MoreLessButton.js.map +2 -2
- package/dist/cjs/package.json +4 -1
- package/dist/cjs/styled.js +10 -6
- package/dist/cjs/styled.js.map +2 -2
- package/dist/cjs/useReadMoreTruncate.js +5 -2
- package/dist/cjs/useReadMoreTruncate.js.map +2 -2
- package/dist/cjs/utils.js +40 -0
- package/dist/cjs/utils.js.map +7 -0
- package/dist/esm/DSReadMore.js +7 -3
- package/dist/esm/DSReadMore.js.map +2 -2
- package/dist/esm/MoreLessButton.js +25 -17
- package/dist/esm/MoreLessButton.js.map +2 -2
- package/dist/esm/package.json +4 -1
- package/dist/esm/styled.js +10 -6
- package/dist/esm/styled.js.map +2 -2
- package/dist/esm/useReadMoreTruncate.js +5 -2
- package/dist/esm/useReadMoreTruncate.js.map +2 -2
- package/dist/esm/utils.js +10 -0
- package/dist/esm/utils.js.map +7 -0
- package/dist/types/styled.d.ts +5 -1
- package/dist/types/utils.d.ts +16 -0
- package/package.json +26 -25
package/dist/cjs/DSReadMore.js
CHANGED
|
@@ -44,6 +44,7 @@ var import_constants = require("./constants/index.js");
|
|
|
44
44
|
var import_react_desc_prop_types = require("./react-desc-prop-types.js");
|
|
45
45
|
var import_styled = require("./styled.js");
|
|
46
46
|
var import_useReadMoreTruncate = require("./useReadMoreTruncate.js");
|
|
47
|
+
var import_utils = require("./utils.js");
|
|
47
48
|
const DSReadMore = (props) => {
|
|
48
49
|
const propsWithDefault = (0, import_ds_props_helpers.useMemoMergePropsWithDefault)(props, import_react_desc_prop_types.defaultProps);
|
|
49
50
|
const { lines, more, less, content, withTooltip, ellipsis, onMore, onLess } = propsWithDefault;
|
|
@@ -68,6 +69,7 @@ const DSReadMore = (props) => {
|
|
|
68
69
|
}
|
|
69
70
|
return `${expanded ? "collapse" : "expand"} the full text for sighted users but the full text is already there for screen readers`;
|
|
70
71
|
}, [expanded, withTooltip]);
|
|
72
|
+
const isSafariBrowser = (0, import_react.useMemo)(() => (0, import_utils.isSafari)(), []);
|
|
71
73
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
72
74
|
import_styled.StyledTextWrapper,
|
|
73
75
|
{
|
|
@@ -76,6 +78,7 @@ const DSReadMore = (props) => {
|
|
|
76
78
|
expanded,
|
|
77
79
|
getOwnerProps,
|
|
78
80
|
getOwnerPropsArguments,
|
|
81
|
+
isSafari: isSafariBrowser,
|
|
79
82
|
...xstyledProps,
|
|
80
83
|
children: [
|
|
81
84
|
srOnlyText,
|
|
@@ -83,13 +86,14 @@ const DSReadMore = (props) => {
|
|
|
83
86
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
84
87
|
import_styled.StyledTextContent,
|
|
85
88
|
{
|
|
86
|
-
lines,
|
|
87
|
-
expanded,
|
|
88
89
|
innerRef: textRef,
|
|
89
90
|
"data-testid": "ds-read_more-text",
|
|
90
91
|
...textProps,
|
|
91
92
|
getOwnerProps,
|
|
92
93
|
getOwnerPropsArguments,
|
|
94
|
+
lines,
|
|
95
|
+
expanded,
|
|
96
|
+
isSafari: isSafariBrowser,
|
|
93
97
|
children: content
|
|
94
98
|
}
|
|
95
99
|
),
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/DSReadMore.tsx", "../../../../../scripts/build/transpile/react-shim.js"],
|
|
4
|
-
"sourcesContent": ["import { ScreenReaderOnly } from '@elliemae/ds-accessibility';\nimport { describe, useGetXstyledProps, useMemoMergePropsWithDefault } from '@elliemae/ds-props-helpers';\nimport { DSTooltipV3 } from '@elliemae/ds-tooltip-v3';\nimport { useCallback } from 'react';\nimport { MoreLessButton } from './MoreLessButton.js';\nimport { DSReadMoreDataTestIds, DSReadMoreName } from './constants/index.js';\nimport { DSReadMorePropTypesSchema, defaultProps, type DSReadMoreT } from './react-desc-prop-types.js';\nimport { StyledTextContent, StyledTextWrapper, StyledTooltipWrapper } from './styled.js';\nimport { useReadMoreTruncate } from './useReadMoreTruncate.js';\n\nconst DSReadMore = (props: DSReadMoreT.Props) => {\n const propsWithDefault = useMemoMergePropsWithDefault<DSReadMoreT.InternalProps>(props, defaultProps);\n const { lines, more, less, content, withTooltip, ellipsis, onMore, onLess } = propsWithDefault;\n\n const xstyledProps = useGetXstyledProps(propsWithDefault);\n\n const { textWrapperRef, textRef, showButton, expanded, setExpanded, textProps, srOnlyText } =\n useReadMoreTruncate(propsWithDefault);\n\n const toggleExpanded = useCallback(\n (newExpanded: boolean) => {\n setExpanded(newExpanded);\n if (newExpanded) {\n onMore();\n } else {\n onLess();\n }\n },\n [onMore, onLess, setExpanded],\n );\n\n const getOwnerProps = useCallback(() => propsWithDefault, [propsWithDefault]);\n const getOwnerPropsArguments = useCallback(() => ({ expanded }), [expanded]);\n const getReadMoreDescribedByText = useCallback(() => {\n if (withTooltip) {\n return 'shows the full text for sighted users but the full text is already there for screen readers';\n }\n return `${expanded ? 'collapse' : 'expand'} the full text for sighted users but the full text is already there for screen readers`;\n }, [expanded, withTooltip]);\n\n return (\n <StyledTextWrapper\n innerRef={textWrapperRef}\n lines={lines}\n expanded={expanded}\n getOwnerProps={getOwnerProps}\n getOwnerPropsArguments={getOwnerPropsArguments}\n {...xstyledProps}\n >\n {srOnlyText}\n {/**\n * Hidden from screen readers unless referenced by `aria-describedby`\n * Ensures the text is only read when associated with a control\n * Prevents it from being focusable or announced independently\n */}\n <ScreenReaderOnly id=\"ds-read_more-text-disclaimer\" aria-hidden>\n {getReadMoreDescribedByText()}\n </ScreenReaderOnly>\n\n <StyledTextContent\n
|
|
5
|
-
"mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;ACAA,YAAuB;
|
|
4
|
+
"sourcesContent": ["import { ScreenReaderOnly } from '@elliemae/ds-accessibility';\nimport { describe, useGetXstyledProps, useMemoMergePropsWithDefault } from '@elliemae/ds-props-helpers';\nimport { DSTooltipV3 } from '@elliemae/ds-tooltip-v3';\nimport { useCallback, useMemo } from 'react';\nimport { MoreLessButton } from './MoreLessButton.js';\nimport { DSReadMoreDataTestIds, DSReadMoreName } from './constants/index.js';\nimport { DSReadMorePropTypesSchema, defaultProps, type DSReadMoreT } from './react-desc-prop-types.js';\nimport { StyledTextContent, StyledTextWrapper, StyledTooltipWrapper } from './styled.js';\nimport { useReadMoreTruncate } from './useReadMoreTruncate.js';\nimport { isSafari } from './utils.js';\n\nconst DSReadMore = (props: DSReadMoreT.Props) => {\n const propsWithDefault = useMemoMergePropsWithDefault<DSReadMoreT.InternalProps>(props, defaultProps);\n const { lines, more, less, content, withTooltip, ellipsis, onMore, onLess } = propsWithDefault;\n\n const xstyledProps = useGetXstyledProps(propsWithDefault);\n\n const { textWrapperRef, textRef, showButton, expanded, setExpanded, textProps, srOnlyText } =\n useReadMoreTruncate(propsWithDefault);\n\n const toggleExpanded = useCallback(\n (newExpanded: boolean) => {\n setExpanded(newExpanded);\n if (newExpanded) {\n onMore();\n } else {\n onLess();\n }\n },\n [onMore, onLess, setExpanded],\n );\n\n const getOwnerProps = useCallback(() => propsWithDefault, [propsWithDefault]);\n const getOwnerPropsArguments = useCallback(() => ({ expanded }), [expanded]);\n const getReadMoreDescribedByText = useCallback(() => {\n if (withTooltip) {\n return 'shows the full text for sighted users but the full text is already there for screen readers';\n }\n return `${expanded ? 'collapse' : 'expand'} the full text for sighted users but the full text is already there for screen readers`;\n }, [expanded, withTooltip]);\n\n const isSafariBrowser = useMemo(() => isSafari(), []);\n\n return (\n <StyledTextWrapper\n innerRef={textWrapperRef}\n lines={lines}\n expanded={expanded}\n getOwnerProps={getOwnerProps}\n getOwnerPropsArguments={getOwnerPropsArguments}\n isSafari={isSafariBrowser}\n {...xstyledProps}\n >\n {srOnlyText}\n {/**\n * Hidden from screen readers unless referenced by `aria-describedby`\n * Ensures the text is only read when associated with a control\n * Prevents it from being focusable or announced independently\n */}\n <ScreenReaderOnly id=\"ds-read_more-text-disclaimer\" aria-hidden>\n {getReadMoreDescribedByText()}\n </ScreenReaderOnly>\n\n <StyledTextContent\n innerRef={textRef}\n data-testid=\"ds-read_more-text\"\n {...textProps}\n getOwnerProps={getOwnerProps}\n getOwnerPropsArguments={getOwnerPropsArguments}\n lines={lines}\n expanded={expanded}\n isSafari={isSafariBrowser}\n >\n {content}\n </StyledTextContent>\n\n {showButton && !withTooltip && (\n <MoreLessButton\n expanded={expanded}\n label={expanded ? less : more}\n onClick={() => toggleExpanded(!expanded)}\n ellipsis={ellipsis}\n dataTestId={DSReadMoreDataTestIds.READ_MORE_BUTTON}\n getOwnerProps={getOwnerProps}\n getOwnerPropsArguments={getOwnerPropsArguments}\n />\n )}\n\n {withTooltip && showButton && (\n <StyledTooltipWrapper getOwnerProps={getOwnerProps} getOwnerPropsArguments={getOwnerPropsArguments}>\n <DSTooltipV3 text={content} wrapWords>\n <MoreLessButton\n expanded={expanded}\n label=\"...\"\n ellipsis={ellipsis}\n withTooltip={withTooltip}\n dataTestId={DSReadMoreDataTestIds.READ_MORE_TOOLTIP_BUTTON}\n getOwnerProps={getOwnerProps}\n getOwnerPropsArguments={getOwnerPropsArguments}\n />\n </DSTooltipV3>\n </StyledTooltipWrapper>\n )}\n </StyledTextWrapper>\n );\n};\n\nDSReadMore.displayName = DSReadMoreName;\nconst DSReadMoreWithSchema = describe(DSReadMore);\nDSReadMoreWithSchema.propTypes = DSReadMorePropTypesSchema;\n\nexport { DSReadMore, DSReadMoreWithSchema };\nexport default DSReadMore;\n", "import * as React from 'react';\nexport { React };\n"],
|
|
5
|
+
"mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;ACAA,YAAuB;AD4CnB;AA5CJ,8BAAiC;AACjC,8BAA2E;AAC3E,2BAA4B;AAC5B,mBAAqC;AACrC,4BAA+B;AAC/B,uBAAsD;AACtD,mCAA0E;AAC1E,oBAA2E;AAC3E,iCAAoC;AACpC,mBAAyB;AAEzB,MAAM,aAAa,CAAC,UAA6B;AAC/C,QAAM,uBAAmB,sDAAwD,OAAO,yCAAY;AACpG,QAAM,EAAE,OAAO,MAAM,MAAM,SAAS,aAAa,UAAU,QAAQ,OAAO,IAAI;AAE9E,QAAM,mBAAe,4CAAmB,gBAAgB;AAExD,QAAM,EAAE,gBAAgB,SAAS,YAAY,UAAU,aAAa,WAAW,WAAW,QACxF,gDAAoB,gBAAgB;AAEtC,QAAM,qBAAiB;AAAA,IACrB,CAAC,gBAAyB;AACxB,kBAAY,WAAW;AACvB,UAAI,aAAa;AACf,eAAO;AAAA,MACT,OAAO;AACL,eAAO;AAAA,MACT;AAAA,IACF;AAAA,IACA,CAAC,QAAQ,QAAQ,WAAW;AAAA,EAC9B;AAEA,QAAM,oBAAgB,0BAAY,MAAM,kBAAkB,CAAC,gBAAgB,CAAC;AAC5E,QAAM,6BAAyB,0BAAY,OAAO,EAAE,SAAS,IAAI,CAAC,QAAQ,CAAC;AAC3E,QAAM,iCAA6B,0BAAY,MAAM;AACnD,QAAI,aAAa;AACf,aAAO;AAAA,IACT;AACA,WAAO,GAAG,WAAW,aAAa,QAAQ;AAAA,EAC5C,GAAG,CAAC,UAAU,WAAW,CAAC;AAE1B,QAAM,sBAAkB,sBAAQ,UAAM,uBAAS,GAAG,CAAC,CAAC;AAEpD,SACE;AAAA,IAAC;AAAA;AAAA,MACC,UAAU;AAAA,MACV;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA,UAAU;AAAA,MACT,GAAG;AAAA,MAEH;AAAA;AAAA,QAMD,4CAAC,4CAAiB,IAAG,gCAA+B,eAAW,MAC5D,qCAA2B,GAC9B;AAAA,QAEA;AAAA,UAAC;AAAA;AAAA,YACC,UAAU;AAAA,YACV,eAAY;AAAA,YACX,GAAG;AAAA,YACJ;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA,YACA,UAAU;AAAA,YAET;AAAA;AAAA,QACH;AAAA,QAEC,cAAc,CAAC,eACd;AAAA,UAAC;AAAA;AAAA,YACC;AAAA,YACA,OAAO,WAAW,OAAO;AAAA,YACzB,SAAS,MAAM,eAAe,CAAC,QAAQ;AAAA,YACvC;AAAA,YACA,YAAY,uCAAsB;AAAA,YAClC;AAAA,YACA;AAAA;AAAA,QACF;AAAA,QAGD,eAAe,cACd,4CAAC,sCAAqB,eAA8B,wBAClD,sDAAC,oCAAY,MAAM,SAAS,WAAS,MACnC;AAAA,UAAC;AAAA;AAAA,YACC;AAAA,YACA,OAAM;AAAA,YACN;AAAA,YACA;AAAA,YACA,YAAY,uCAAsB;AAAA,YAClC;AAAA,YACA;AAAA;AAAA,QACF,GACF,GACF;AAAA;AAAA;AAAA,EAEJ;AAEJ;AAEA,WAAW,cAAc;AACzB,MAAM,2BAAuB,kCAAS,UAAU;AAChD,qBAAqB,YAAY;AAGjC,IAAO,qBAAQ;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -36,22 +36,30 @@ var import_jsx_runtime = require("react/jsx-runtime");
|
|
|
36
36
|
var import_styled = require("./styled.js");
|
|
37
37
|
const MoreLessButton = (props) => {
|
|
38
38
|
const { expanded, label, onClick, ellipsis, withTooltip, dataTestId, getOwnerProps, getOwnerPropsArguments } = props;
|
|
39
|
-
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
39
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
40
|
+
import_styled.StyledButtonWrapper,
|
|
41
|
+
{
|
|
42
|
+
expanded,
|
|
43
|
+
getOwnerProps,
|
|
44
|
+
getOwnerPropsArguments,
|
|
45
|
+
children: [
|
|
46
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { "aria-hidden": true, children: expanded || withTooltip ? " " : ellipsis }),
|
|
47
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
48
|
+
import_styled.StyledButton,
|
|
49
|
+
{
|
|
50
|
+
withTooltip,
|
|
51
|
+
buttonType: "text",
|
|
52
|
+
onClick,
|
|
53
|
+
size: "s",
|
|
54
|
+
"data-testid": dataTestId,
|
|
55
|
+
getOwnerProps,
|
|
56
|
+
getOwnerPropsArguments,
|
|
57
|
+
"aria-describedby": "ds-read_more-text-disclaimer",
|
|
58
|
+
children: label
|
|
59
|
+
}
|
|
60
|
+
)
|
|
61
|
+
]
|
|
62
|
+
}
|
|
63
|
+
);
|
|
56
64
|
};
|
|
57
65
|
//# sourceMappingURL=MoreLessButton.js.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/MoreLessButton.tsx", "../../../../../scripts/build/transpile/react-shim.js"],
|
|
4
|
-
"sourcesContent": ["import React from 'react';\nimport { StyledButton, StyledButtonWrapper } from './styled.js';\n\ninterface MoreLessButtonProps {\n expanded: boolean;\n label: string;\n onClick?: () => void;\n ellipsis: string;\n withTooltip?: boolean;\n dataTestId: string;\n getOwnerProps: () => object;\n getOwnerPropsArguments: () => object;\n}\n\nconst MoreLessButton = (props: MoreLessButtonProps) => {\n const { expanded, label, onClick, ellipsis, withTooltip, dataTestId, getOwnerProps, getOwnerPropsArguments } = props;\n\n return (\n <StyledButtonWrapper
|
|
5
|
-
"mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;ACAA,YAAuB;ADkBnB;AAjBJ,oBAAkD;AAalD,MAAM,iBAAiB,CAAC,UAA+B;AACrD,QAAM,EAAE,UAAU,OAAO,SAAS,UAAU,aAAa,YAAY,eAAe,uBAAuB,IAAI;AAE/G,SACE,
|
|
4
|
+
"sourcesContent": ["import React from 'react';\nimport { StyledButton, StyledButtonWrapper } from './styled.js';\n\ninterface MoreLessButtonProps {\n expanded: boolean;\n label: string;\n onClick?: () => void;\n ellipsis: string;\n withTooltip?: boolean;\n dataTestId: string;\n getOwnerProps: () => object;\n getOwnerPropsArguments: () => object;\n}\n\nconst MoreLessButton = (props: MoreLessButtonProps) => {\n const { expanded, label, onClick, ellipsis, withTooltip, dataTestId, getOwnerProps, getOwnerPropsArguments } = props;\n\n return (\n <StyledButtonWrapper\n expanded={expanded}\n getOwnerProps={getOwnerProps}\n getOwnerPropsArguments={getOwnerPropsArguments}\n >\n <span aria-hidden>{expanded || withTooltip ? ' ' : ellipsis}</span>\n\n <StyledButton\n withTooltip={withTooltip}\n buttonType=\"text\"\n onClick={onClick}\n size=\"s\"\n data-testid={dataTestId}\n getOwnerProps={getOwnerProps}\n getOwnerPropsArguments={getOwnerPropsArguments}\n aria-describedby=\"ds-read_more-text-disclaimer\"\n >\n {label}\n </StyledButton>\n </StyledButtonWrapper>\n );\n};\n\nexport { MoreLessButton };\n", "import * as React from 'react';\nexport { React };\n"],
|
|
5
|
+
"mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;ACAA,YAAuB;ADkBnB;AAjBJ,oBAAkD;AAalD,MAAM,iBAAiB,CAAC,UAA+B;AACrD,QAAM,EAAE,UAAU,OAAO,SAAS,UAAU,aAAa,YAAY,eAAe,uBAAuB,IAAI;AAE/G,SACE;AAAA,IAAC;AAAA;AAAA,MACC;AAAA,MACA;AAAA,MACA;AAAA,MAEA;AAAA,oDAAC,UAAK,eAAW,MAAE,sBAAY,cAAc,MAAM,UAAS;AAAA,QAE5D;AAAA,UAAC;AAAA;AAAA,YACC;AAAA,YACA,YAAW;AAAA,YACX;AAAA,YACA,MAAK;AAAA,YACL,eAAa;AAAA,YACb;AAAA,YACA;AAAA,YACA,oBAAiB;AAAA,YAEhB;AAAA;AAAA,QACH;AAAA;AAAA;AAAA,EACF;AAEJ;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/dist/cjs/package.json
CHANGED
package/dist/cjs/styled.js
CHANGED
|
@@ -51,10 +51,9 @@ const StyledTextWrapper = (0, import_ds_system.styled)("span", { name: import_co
|
|
|
51
51
|
* - Avoids -webkit-line-clamp: 1 which breaks layout and can cause infinite ResizeObserver loops.
|
|
52
52
|
* - Instead, uses flex + white-space + overflow hidden for single-line truncation.
|
|
53
53
|
*/
|
|
54
|
-
${({ lines, expanded }) => lines === 1 && !expanded ? `
|
|
54
|
+
${({ lines, isSafari, expanded }) => lines === 1 && isSafari && !expanded ? `
|
|
55
55
|
display: flex;
|
|
56
56
|
align-items: baseline;
|
|
57
|
-
overflow: hidden;
|
|
58
57
|
white-space: nowrap;
|
|
59
58
|
` : `
|
|
60
59
|
display: -webkit-box;
|
|
@@ -71,9 +70,9 @@ const StyledTextWrapper = (0, import_ds_system.styled)("span", { name: import_co
|
|
|
71
70
|
${import_ds_system.xStyledCommonProps}
|
|
72
71
|
`;
|
|
73
72
|
const StyledTextContent = (0, import_ds_system.styled)("span", { name: import_constants.DSReadMoreName, slot: import_constants.DSReadMoreSlots.TEXT_CONTENT })`
|
|
74
|
-
${({ lines, expanded }) => lines === 1 && !expanded ? `
|
|
73
|
+
${({ lines, expanded, isSafari }) => lines === 1 && isSafari && !expanded ? `
|
|
75
74
|
flex: 1 1 auto;
|
|
76
|
-
overflow:
|
|
75
|
+
overflow-x: clip;
|
|
77
76
|
white-space: nowrap;
|
|
78
77
|
text-overflow: clip;
|
|
79
78
|
` : ""}
|
|
@@ -81,9 +80,14 @@ const StyledTextContent = (0, import_ds_system.styled)("span", { name: import_co
|
|
|
81
80
|
const StyledTooltipWrapper = (0, import_ds_system.styled)("div", { name: import_constants.DSReadMoreName, slot: import_constants.DSReadMoreSlots.TOOLTIP_WRAPPER })`
|
|
82
81
|
display: inline-block;
|
|
83
82
|
`;
|
|
84
|
-
const StyledButtonWrapper = (0, import_ds_system.styled)("span", { name: import_constants.DSReadMoreName, slot: import_constants.DSReadMoreSlots.BUTTON_WRAPPER })
|
|
83
|
+
const StyledButtonWrapper = (0, import_ds_system.styled)("span", { name: import_constants.DSReadMoreName, slot: import_constants.DSReadMoreSlots.BUTTON_WRAPPER })`
|
|
84
|
+
flex: none;
|
|
85
|
+
display: -webkit-inline-box;
|
|
86
|
+
-webkit-line-clamp: 2;
|
|
87
|
+
-webkit-box-orient: vertical;
|
|
88
|
+
`;
|
|
85
89
|
const StyledButton = (0, import_ds_system.styled)(import_ds_button_v2.DSButtonV3, { name: import_constants.DSReadMoreName, slot: import_constants.DSReadMoreSlots.BUTTON })`
|
|
86
|
-
padding: 0;
|
|
90
|
+
padding: ${({ theme }) => `0 ${theme.space.xxxs}`};
|
|
87
91
|
color: ${({ withTooltip, theme }) => withTooltip ? "inherit" : `${theme.colors.brand["600"]}`};
|
|
88
92
|
border: 0;
|
|
89
93
|
margin: 0;
|
package/dist/cjs/styled.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/styled.tsx", "../../../../../scripts/build/transpile/react-shim.js"],
|
|
4
|
-
"sourcesContent": ["import { styled, xStyledCommonProps } from '@elliemae/ds-system';\nimport { DSButtonV3 } from '@elliemae/ds-button-v2';\nimport { DSReadMoreName, DSReadMoreSlots } from './constants/index.js';\n\nexport const StyledTextWrapper = styled('span', { name: DSReadMoreName, slot: DSReadMoreSlots.ROOT })<{\n lines: number;\n expanded: boolean;\n}>`\n position: relative;\n /**\n * PUI-16110 - [ReadMore] Autotriggered-Visually broken in safari + Sequoia\n * -----------------------------------\n * In Safari 17.4+ (macOS Sequoia), using scrollHeight or offsetHeight with line-clamp\n * can break the layout and, potentially, trigger infinite loops through ResizeObserver.\n *\n * - Avoids -webkit-line-clamp: 1 which breaks layout and can cause infinite ResizeObserver loops.\n * - Instead, uses flex + white-space + overflow hidden for single-line truncation.\n */\n ${({ lines, expanded }) =>\n lines === 1 && !expanded\n ? `\n display: flex;\n align-items: baseline;\n
|
|
5
|
-
"mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;ACAA,YAAuB;ADAvB,uBAA2C;AAC3C,0BAA2B;AAC3B,uBAAgD;AAEzC,MAAM,wBAAoB,yBAAO,QAAQ,EAAE,MAAM,iCAAgB,MAAM,iCAAgB,KAAK,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,
|
|
4
|
+
"sourcesContent": ["import { styled, xStyledCommonProps } from '@elliemae/ds-system';\nimport { DSButtonV3 } from '@elliemae/ds-button-v2';\nimport { DSReadMoreName, DSReadMoreSlots } from './constants/index.js';\n\nexport const StyledTextWrapper = styled('span', { name: DSReadMoreName, slot: DSReadMoreSlots.ROOT })<{\n lines: number;\n expanded: boolean;\n isSafari: boolean;\n}>`\n position: relative;\n /**\n * PUI-16110 - [ReadMore] Autotriggered-Visually broken in safari + Sequoia\n * -----------------------------------\n * In Safari 17.4+ (macOS Sequoia), using scrollHeight or offsetHeight with line-clamp\n * can break the layout and, potentially, trigger infinite loops through ResizeObserver.\n *\n * - Avoids -webkit-line-clamp: 1 which breaks layout and can cause infinite ResizeObserver loops.\n * - Instead, uses flex + white-space + overflow hidden for single-line truncation.\n */\n ${({ lines, isSafari, expanded }) =>\n lines === 1 && isSafari && !expanded\n ? `\n display: flex;\n align-items: baseline;\n white-space: nowrap;\n `\n : `\n display: -webkit-box;\n -webkit-line-clamp: ${expanded ? 'unset' : lines};\n -webkit-box-orient: vertical;\n `}\n\n -webkit-hyphens: auto;\n -moz-hyphens: auto;\n -ms-hyphens: auto;\n hyphens: auto;\n overflow-wrap: break-word;\n white-space: pre-wrap;\n ${xStyledCommonProps}\n`;\n\nexport const StyledTextContent = styled('span', { name: DSReadMoreName, slot: DSReadMoreSlots.TEXT_CONTENT })<{\n lines: number;\n expanded: boolean;\n isSafari: boolean;\n}>`\n ${({ lines, expanded, isSafari }) =>\n lines === 1 && isSafari && !expanded\n ? `\n flex: 1 1 auto;\n overflow-x: clip;\n white-space: nowrap;\n text-overflow: clip;\n `\n : ''}\n`;\n\nexport const StyledTooltipWrapper = styled('div', { name: DSReadMoreName, slot: DSReadMoreSlots.TOOLTIP_WRAPPER })`\n display: inline-block;\n`;\n\nexport const StyledButtonWrapper = styled('span', { name: DSReadMoreName, slot: DSReadMoreSlots.BUTTON_WRAPPER })<{\n expanded: boolean;\n}>`\n flex: none;\n display: -webkit-inline-box;\n -webkit-line-clamp: 2;\n -webkit-box-orient: vertical;\n`;\n\nexport const StyledButton = styled(DSButtonV3, { name: DSReadMoreName, slot: DSReadMoreSlots.BUTTON })<{\n withTooltip?: boolean;\n}>`\n padding: ${({ theme }) => `0 ${theme.space.xxxs}`};\n color: ${({ withTooltip, theme }) => (withTooltip ? 'inherit' : `${theme.colors.brand['600']}`)};\n border: 0;\n margin: 0;\n font-size: 12px;\n min-width: 0px;\n height: auto;\n text-transform: unset;\n &:hover:not([disabled]) {\n color: ${({ withTooltip, theme }) => (withTooltip ? 'inherit' : `${theme.colors.brand['600']}`)};\n background-color: transparent;\n text-decoration: ${({ withTooltip }) => (withTooltip ? '' : 'underline')};\n }\n`;\n\nexport const ScreenReaderOnly = styled.span`\n clip: rect(0 0 0 0);\n clip-path: inset(50%);\n height: 100%;\n overflow: hidden;\n position: absolute;\n width: 100%;\n`;\n", "import * as React from 'react';\nexport { React };\n"],
|
|
5
|
+
"mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;ACAA,YAAuB;ADAvB,uBAA2C;AAC3C,0BAA2B;AAC3B,uBAAgD;AAEzC,MAAM,wBAAoB,yBAAO,QAAQ,EAAE,MAAM,iCAAgB,MAAM,iCAAgB,KAAK,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAehG,CAAC,EAAE,OAAO,UAAU,SAAS,MAC7B,UAAU,KAAK,YAAY,CAAC,WACxB;AAAA;AAAA;AAAA;AAAA,MAKA;AAAA;AAAA,0BAEkB,WAAW,UAAU,KAAK;AAAA;AAAA,GAEjD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAQC,mCAAkB;AAAA;AAGf,MAAM,wBAAoB,yBAAO,QAAQ,EAAE,MAAM,iCAAgB,MAAM,iCAAgB,aAAa,CAAC;AAAA,IAKxG,CAAC,EAAE,OAAO,UAAU,SAAS,MAC7B,UAAU,KAAK,YAAY,CAAC,WACxB;AAAA;AAAA;AAAA;AAAA;AAAA,MAMA,EAAE;AAAA;AAGH,MAAM,2BAAuB,yBAAO,OAAO,EAAE,MAAM,iCAAgB,MAAM,iCAAgB,gBAAgB,CAAC;AAAA;AAAA;AAI1G,MAAM,0BAAsB,yBAAO,QAAQ,EAAE,MAAM,iCAAgB,MAAM,iCAAgB,eAAe,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA;AASzG,MAAM,mBAAe,yBAAO,gCAAY,EAAE,MAAM,iCAAgB,MAAM,iCAAgB,OAAO,CAAC;AAAA,aAGxF,CAAC,EAAE,MAAM,MAAM,KAAK,MAAM,MAAM,IAAI,EAAE;AAAA,WACxC,CAAC,EAAE,aAAa,MAAM,MAAO,cAAc,YAAY,GAAG,MAAM,OAAO,MAAM,KAAK,CAAC,EAAG;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,aAQpF,CAAC,EAAE,aAAa,MAAM,MAAO,cAAc,YAAY,GAAG,MAAM,OAAO,MAAM,KAAK,CAAC,EAAG;AAAA;AAAA,uBAE5E,CAAC,EAAE,YAAY,MAAO,cAAc,KAAK,WAAY;AAAA;AAAA;AAIrE,MAAM,mBAAmB,wBAAO;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -36,6 +36,7 @@ var import_jsx_runtime = require("react/jsx-runtime");
|
|
|
36
36
|
var import_react = require("react");
|
|
37
37
|
var import_useOnElementResize = require("./useOnElementResize.js");
|
|
38
38
|
var import_styled = require("./styled.js");
|
|
39
|
+
var import_utils = require("./utils.js");
|
|
39
40
|
const overflows = ({ offsetHeight, scrollHeight }) => offsetHeight + 4 < scrollHeight;
|
|
40
41
|
const binSearchTextOverflow = (element, parent, content) => {
|
|
41
42
|
let left = 0;
|
|
@@ -65,7 +66,7 @@ const useReadMoreTruncate = ({ lines, content }) => {
|
|
|
65
66
|
const wrapperEl = textWrapperRef.current;
|
|
66
67
|
if (!textEl || !wrapperEl) return;
|
|
67
68
|
textEl.innerText = content;
|
|
68
|
-
if (lines === 1) {
|
|
69
|
+
if (lines === 1 && (0, import_utils.isSafari)()) {
|
|
69
70
|
const computed = getComputedStyle(textEl);
|
|
70
71
|
const font = `${computed.fontWeight} ${computed.fontSize} ${computed.fontFamily}`;
|
|
71
72
|
const canvas = document.createElement("canvas");
|
|
@@ -87,7 +88,9 @@ const useReadMoreTruncate = ({ lines, content }) => {
|
|
|
87
88
|
if (!expanded && isOverflowing) {
|
|
88
89
|
const doBinaryTruncate = () => {
|
|
89
90
|
if (!textEl || !wrapperEl) return;
|
|
90
|
-
|
|
91
|
+
requestAnimationFrame(() => {
|
|
92
|
+
binSearchTextOverflow(textEl, wrapperEl, content);
|
|
93
|
+
});
|
|
91
94
|
};
|
|
92
95
|
if ("fonts" in document && document.fonts.ready) {
|
|
93
96
|
document.fonts.ready.then(() => {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/useReadMoreTruncate.tsx", "../../../../../scripts/build/transpile/react-shim.js"],
|
|
4
|
-
"sourcesContent": ["/* eslint-disable @typescript-eslint/no-misused-promises */\n/* eslint-disable max-statements */\n/* eslint-disable complexity */\n// useReadMoreTruncate.tsx\nimport React, { useLayoutEffect, useMemo, useRef, useState } from 'react';\nimport { useOnElementResize } from './useOnElementResize.js';\nimport type { DSReadMoreT } from './react-desc-prop-types.js';\nimport { ScreenReaderOnly } from './styled.js';\n\n// offsetHeight + margin/buffer to avoid subpixel errors\nconst overflows = ({ offsetHeight, scrollHeight }: { offsetHeight: number; scrollHeight: number }) =>\n offsetHeight + 4 < scrollHeight;\n\n/**\n * binSearchTextOverflow slices the content until there's no overflow.\n * It assumes `element` is the <span> node containing the text, and `parent`\n * is the wrapper with the height/clamp applied.\n */\nconst binSearchTextOverflow = (element: HTMLElement, parent: HTMLElement, content: string) => {\n let left = 0;\n let right = content.length;\n let textEnd = 0;\n\n // Place the full text before starting\n element.innerText = content;\n\n while (left <= right) {\n const mid = Math.floor((left + right) / 2);\n element.innerText = content.slice(0, mid);\n\n if (overflows(parent)) {\n // Still overflowing \u2192 reduce\n right = mid - 1;\n } else {\n // No overflow \u2192 we can increase\n textEnd = mid;\n left = mid + 1;\n }\n }\n\n element.innerText = content.slice(0, textEnd);\n};\n\nexport const useReadMoreTruncate = ({ lines, content }: Pick<DSReadMoreT.InternalProps, 'lines' | 'content'>) => {\n const textWrapperRef = useRef<HTMLSpanElement>(null);\n const textRef = useRef<HTMLSpanElement>(null);\n\n const [expanded, setExpanded] = useState(false);\n const [showButton, setShowButton] = useState(false);\n\n // Detect container size changes\n const { width, height } = useOnElementResize(textWrapperRef);\n\n useLayoutEffect(() => {\n const textEl = textRef.current;\n const wrapperEl = textWrapperRef.current;\n if (!textEl || !wrapperEl) return;\n\n // 1) Always reset the text to \"content\"\n textEl.innerText = content;\n\n // 2) Case lines === 1: measure with canvas\n if (lines === 1) {\n const computed = getComputedStyle(textEl);\n const font = `${computed.fontWeight} ${computed.fontSize} ${computed.fontFamily}`;\n\n const canvas = document.createElement('canvas');\n const ctx = canvas.getContext('2d');\n let truncated = false;\n\n if (ctx) {\n ctx.font = font;\n const measuredWidth = ctx.measureText(content).width;\n truncated = measuredWidth > wrapperEl.clientWidth;\n }\n\n setShowButton(expanded || truncated);\n // No need to truncate char by char in single-line: hiding with CSS is enough.\n return;\n }\n\n // 3) For multiline: first check native overflow\n const isOverflowing = overflows({\n offsetHeight: wrapperEl.offsetHeight,\n scrollHeight: wrapperEl.scrollHeight,\n });\n setShowButton(expanded || isOverflowing);\n\n // 4) If not expanded and there is overflow, adjust the slice with binary search\n if (!expanded && isOverflowing) {\n // Function that executes binSearchTextOverflow\n const doBinaryTruncate = () => {\n if (!textEl || !wrapperEl) return;\n binSearchTextOverflow(textEl, wrapperEl, content);\n };\n\n // If the browser supports document.fonts, wait for them to load.\n if ('fonts' in document && document.fonts.ready) {\n document.fonts.ready\n .then(() => {\n doBinaryTruncate();\n })\n .catch(() => {\n // If fonts.ready fails, fall back to timeout\n setTimeout(doBinaryTruncate, 0);\n });\n } else {\n // Fallback for browsers without the Font Loading API\n setTimeout(doBinaryTruncate, 0);\n }\n }\n }, [lines, content, expanded, width, height]);\n\n // Hidden text for screen readers (always the full string)\n const srOnlyText = useMemo(() => <ScreenReaderOnly>{content}</ScreenReaderOnly>, [content]);\n\n // Mark the visible content as aria-hidden\n const textProps = useMemo(() => ({ 'aria-hidden': true }), []);\n\n return {\n textWrapperRef,\n textRef,\n showButton,\n expanded,\n setExpanded,\n textProps,\n srOnlyText,\n };\n};\n", "import * as React from 'react';\nexport { React };\n"],
|
|
5
|
-
"mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;ACAA,YAAuB;
|
|
4
|
+
"sourcesContent": ["/* eslint-disable @typescript-eslint/no-misused-promises */\n/* eslint-disable max-statements */\n/* eslint-disable complexity */\n// useReadMoreTruncate.tsx\nimport React, { useLayoutEffect, useMemo, useRef, useState } from 'react';\nimport { useOnElementResize } from './useOnElementResize.js';\nimport type { DSReadMoreT } from './react-desc-prop-types.js';\nimport { ScreenReaderOnly } from './styled.js';\nimport { isSafari } from './utils.js';\n\n// offsetHeight + margin/buffer to avoid subpixel errors\nconst overflows = ({ offsetHeight, scrollHeight }: { offsetHeight: number; scrollHeight: number }) =>\n offsetHeight + 4 < scrollHeight;\n\n/**\n * binSearchTextOverflow slices the content until there's no overflow.\n * It assumes `element` is the <span> node containing the text, and `parent`\n * is the wrapper with the height/clamp applied.\n */\nconst binSearchTextOverflow = (element: HTMLElement, parent: HTMLElement, content: string) => {\n let left = 0;\n let right = content.length;\n let textEnd = 0;\n\n // Place the full text before starting\n element.innerText = content;\n\n while (left <= right) {\n const mid = Math.floor((left + right) / 2);\n element.innerText = content.slice(0, mid);\n\n if (overflows(parent)) {\n // Still overflowing \u2192 reduce\n right = mid - 1;\n } else {\n // No overflow \u2192 we can increase\n textEnd = mid;\n left = mid + 1;\n }\n }\n\n element.innerText = content.slice(0, textEnd);\n};\n\nexport const useReadMoreTruncate = ({ lines, content }: Pick<DSReadMoreT.InternalProps, 'lines' | 'content'>) => {\n const textWrapperRef = useRef<HTMLSpanElement>(null);\n const textRef = useRef<HTMLSpanElement>(null);\n\n const [expanded, setExpanded] = useState(false);\n const [showButton, setShowButton] = useState(false);\n\n // Detect container size changes\n const { width, height } = useOnElementResize(textWrapperRef);\n\n useLayoutEffect(() => {\n const textEl = textRef.current;\n const wrapperEl = textWrapperRef.current;\n if (!textEl || !wrapperEl) return;\n\n // 1) Always reset the text to \"content\"\n textEl.innerText = content;\n\n // 2) Case lines === 1: measure with canvas\n if (lines === 1 && isSafari()) {\n const computed = getComputedStyle(textEl);\n const font = `${computed.fontWeight} ${computed.fontSize} ${computed.fontFamily}`;\n\n const canvas = document.createElement('canvas');\n const ctx = canvas.getContext('2d');\n let truncated = false;\n\n if (ctx) {\n ctx.font = font;\n const measuredWidth = ctx.measureText(content).width;\n truncated = measuredWidth > wrapperEl.clientWidth;\n }\n\n setShowButton(expanded || truncated);\n // No need to truncate char by char in single-line: hiding with CSS is enough.\n return;\n }\n\n // 3) For multiline: first check native overflow\n const isOverflowing = overflows({\n offsetHeight: wrapperEl.offsetHeight,\n scrollHeight: wrapperEl.scrollHeight,\n });\n setShowButton(expanded || isOverflowing);\n\n // 4) If not expanded and there is overflow, adjust the slice with binary search\n if (!expanded && isOverflowing) {\n // Function that executes binSearchTextOverflow\n const doBinaryTruncate = () => {\n if (!textEl || !wrapperEl) return;\n requestAnimationFrame(() => {\n binSearchTextOverflow(textEl, wrapperEl, content);\n });\n };\n\n // If the browser supports document.fonts, wait for them to load.\n if ('fonts' in document && document.fonts.ready) {\n document.fonts.ready\n .then(() => {\n doBinaryTruncate();\n })\n .catch(() => {\n // If fonts.ready fails, fall back to timeout\n setTimeout(doBinaryTruncate, 0);\n });\n } else {\n // Fallback for browsers without the Font Loading API\n setTimeout(doBinaryTruncate, 0);\n }\n }\n }, [lines, content, expanded, width, height]);\n\n // Hidden text for screen readers (always the full string)\n const srOnlyText = useMemo(() => <ScreenReaderOnly>{content}</ScreenReaderOnly>, [content]);\n\n // Mark the visible content as aria-hidden\n const textProps = useMemo(() => ({ 'aria-hidden': true }), []);\n\n return {\n textWrapperRef,\n textRef,\n showButton,\n expanded,\n setExpanded,\n textProps,\n srOnlyText,\n };\n};\n", "import * as React from 'react';\nexport { React };\n"],
|
|
5
|
+
"mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;ACAA,YAAuB;ADqHY;AAjHnC,mBAAkE;AAClE,gCAAmC;AAEnC,oBAAiC;AACjC,mBAAyB;AAGzB,MAAM,YAAY,CAAC,EAAE,cAAc,aAAa,MAC9C,eAAe,IAAI;AAOrB,MAAM,wBAAwB,CAAC,SAAsB,QAAqB,YAAoB;AAC5F,MAAI,OAAO;AACX,MAAI,QAAQ,QAAQ;AACpB,MAAI,UAAU;AAGd,UAAQ,YAAY;AAEpB,SAAO,QAAQ,OAAO;AACpB,UAAM,MAAM,KAAK,OAAO,OAAO,SAAS,CAAC;AACzC,YAAQ,YAAY,QAAQ,MAAM,GAAG,GAAG;AAExC,QAAI,UAAU,MAAM,GAAG;AAErB,cAAQ,MAAM;AAAA,IAChB,OAAO;AAEL,gBAAU;AACV,aAAO,MAAM;AAAA,IACf;AAAA,EACF;AAEA,UAAQ,YAAY,QAAQ,MAAM,GAAG,OAAO;AAC9C;AAEO,MAAM,sBAAsB,CAAC,EAAE,OAAO,QAAQ,MAA4D;AAC/G,QAAM,qBAAiB,qBAAwB,IAAI;AACnD,QAAM,cAAU,qBAAwB,IAAI;AAE5C,QAAM,CAAC,UAAU,WAAW,QAAI,uBAAS,KAAK;AAC9C,QAAM,CAAC,YAAY,aAAa,QAAI,uBAAS,KAAK;AAGlD,QAAM,EAAE,OAAO,OAAO,QAAI,8CAAmB,cAAc;AAE3D,oCAAgB,MAAM;AACpB,UAAM,SAAS,QAAQ;AACvB,UAAM,YAAY,eAAe;AACjC,QAAI,CAAC,UAAU,CAAC,UAAW;AAG3B,WAAO,YAAY;AAGnB,QAAI,UAAU,SAAK,uBAAS,GAAG;AAC7B,YAAM,WAAW,iBAAiB,MAAM;AACxC,YAAM,OAAO,GAAG,SAAS,UAAU,IAAI,SAAS,QAAQ,IAAI,SAAS,UAAU;AAE/E,YAAM,SAAS,SAAS,cAAc,QAAQ;AAC9C,YAAM,MAAM,OAAO,WAAW,IAAI;AAClC,UAAI,YAAY;AAEhB,UAAI,KAAK;AACP,YAAI,OAAO;AACX,cAAM,gBAAgB,IAAI,YAAY,OAAO,EAAE;AAC/C,oBAAY,gBAAgB,UAAU;AAAA,MACxC;AAEA,oBAAc,YAAY,SAAS;AAEnC;AAAA,IACF;AAGA,UAAM,gBAAgB,UAAU;AAAA,MAC9B,cAAc,UAAU;AAAA,MACxB,cAAc,UAAU;AAAA,IAC1B,CAAC;AACD,kBAAc,YAAY,aAAa;AAGvC,QAAI,CAAC,YAAY,eAAe;AAE9B,YAAM,mBAAmB,MAAM;AAC7B,YAAI,CAAC,UAAU,CAAC,UAAW;AAC3B,8BAAsB,MAAM;AAC1B,gCAAsB,QAAQ,WAAW,OAAO;AAAA,QAClD,CAAC;AAAA,MACH;AAGA,UAAI,WAAW,YAAY,SAAS,MAAM,OAAO;AAC/C,iBAAS,MAAM,MACZ,KAAK,MAAM;AACV,2BAAiB;AAAA,QACnB,CAAC,EACA,MAAM,MAAM;AAEX,qBAAW,kBAAkB,CAAC;AAAA,QAChC,CAAC;AAAA,MACL,OAAO;AAEL,mBAAW,kBAAkB,CAAC;AAAA,MAChC;AAAA,IACF;AAAA,EACF,GAAG,CAAC,OAAO,SAAS,UAAU,OAAO,MAAM,CAAC;AAG5C,QAAM,iBAAa,sBAAQ,MAAM,4CAAC,kCAAkB,mBAAQ,GAAqB,CAAC,OAAO,CAAC;AAG1F,QAAM,gBAAY,sBAAQ,OAAO,EAAE,eAAe,KAAK,IAAI,CAAC,CAAC;AAE7D,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
var utils_exports = {};
|
|
30
|
+
__export(utils_exports, {
|
|
31
|
+
isSafari: () => isSafari
|
|
32
|
+
});
|
|
33
|
+
module.exports = __toCommonJS(utils_exports);
|
|
34
|
+
var React = __toESM(require("react"));
|
|
35
|
+
const isSafari = () => {
|
|
36
|
+
if (typeof navigator === "undefined") return false;
|
|
37
|
+
const ua = navigator.userAgent;
|
|
38
|
+
return /safari/i.test(ua) && !/chrome|chromium|android/i.test(ua);
|
|
39
|
+
};
|
|
40
|
+
//# sourceMappingURL=utils.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/utils.ts", "../../../../../scripts/build/transpile/react-shim.js"],
|
|
4
|
+
"sourcesContent": ["/**\n * Detects whether the current browser is Safari (excluding Chrome/Chromium and Android browsers).\n *\n * This utility is used to apply Safari-specific layout and styling workarounds.\n * Safari has known differences and bugs around text truncation, line-clamp,\n * and layout measurements (e.g. offsetHeight/scrollHeight), which can affect\n * components that rely on precise DOM sizing.\n *\n * The check is intentionally lightweight and user-agent based, as feature\n * detection is not reliable for these Safari-specific rendering issues.\n *\n * When used inside React components, this function should be evaluated once\n * (e.g. via useMemo) to avoid layout churn and focus instability caused by\n * re-evaluating browser detection on every render.\n */\nexport const isSafari = () => {\n if (typeof navigator === 'undefined') return false;\n\n const ua = navigator.userAgent;\n // Safari but not Chrome/Chromium/Android\n return /safari/i.test(ua) && !/chrome|chromium|android/i.test(ua);\n};\n", "import * as React from 'react';\nexport { React };\n"],
|
|
5
|
+
"mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;ACAA,YAAuB;ADehB,MAAM,WAAW,MAAM;AAC5B,MAAI,OAAO,cAAc,YAAa,QAAO;AAE7C,QAAM,KAAK,UAAU;AAErB,SAAO,UAAU,KAAK,EAAE,KAAK,CAAC,2BAA2B,KAAK,EAAE;AAClE;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
package/dist/esm/DSReadMore.js
CHANGED
|
@@ -3,12 +3,13 @@ import { jsx, jsxs } from "react/jsx-runtime";
|
|
|
3
3
|
import { ScreenReaderOnly } from "@elliemae/ds-accessibility";
|
|
4
4
|
import { describe, useGetXstyledProps, useMemoMergePropsWithDefault } from "@elliemae/ds-props-helpers";
|
|
5
5
|
import { DSTooltipV3 } from "@elliemae/ds-tooltip-v3";
|
|
6
|
-
import { useCallback } from "react";
|
|
6
|
+
import { useCallback, useMemo } from "react";
|
|
7
7
|
import { MoreLessButton } from "./MoreLessButton.js";
|
|
8
8
|
import { DSReadMoreDataTestIds, DSReadMoreName } from "./constants/index.js";
|
|
9
9
|
import { DSReadMorePropTypesSchema, defaultProps } from "./react-desc-prop-types.js";
|
|
10
10
|
import { StyledTextContent, StyledTextWrapper, StyledTooltipWrapper } from "./styled.js";
|
|
11
11
|
import { useReadMoreTruncate } from "./useReadMoreTruncate.js";
|
|
12
|
+
import { isSafari } from "./utils.js";
|
|
12
13
|
const DSReadMore = (props) => {
|
|
13
14
|
const propsWithDefault = useMemoMergePropsWithDefault(props, defaultProps);
|
|
14
15
|
const { lines, more, less, content, withTooltip, ellipsis, onMore, onLess } = propsWithDefault;
|
|
@@ -33,6 +34,7 @@ const DSReadMore = (props) => {
|
|
|
33
34
|
}
|
|
34
35
|
return `${expanded ? "collapse" : "expand"} the full text for sighted users but the full text is already there for screen readers`;
|
|
35
36
|
}, [expanded, withTooltip]);
|
|
37
|
+
const isSafariBrowser = useMemo(() => isSafari(), []);
|
|
36
38
|
return /* @__PURE__ */ jsxs(
|
|
37
39
|
StyledTextWrapper,
|
|
38
40
|
{
|
|
@@ -41,6 +43,7 @@ const DSReadMore = (props) => {
|
|
|
41
43
|
expanded,
|
|
42
44
|
getOwnerProps,
|
|
43
45
|
getOwnerPropsArguments,
|
|
46
|
+
isSafari: isSafariBrowser,
|
|
44
47
|
...xstyledProps,
|
|
45
48
|
children: [
|
|
46
49
|
srOnlyText,
|
|
@@ -48,13 +51,14 @@ const DSReadMore = (props) => {
|
|
|
48
51
|
/* @__PURE__ */ jsx(
|
|
49
52
|
StyledTextContent,
|
|
50
53
|
{
|
|
51
|
-
lines,
|
|
52
|
-
expanded,
|
|
53
54
|
innerRef: textRef,
|
|
54
55
|
"data-testid": "ds-read_more-text",
|
|
55
56
|
...textProps,
|
|
56
57
|
getOwnerProps,
|
|
57
58
|
getOwnerPropsArguments,
|
|
59
|
+
lines,
|
|
60
|
+
expanded,
|
|
61
|
+
isSafari: isSafariBrowser,
|
|
58
62
|
children: content
|
|
59
63
|
}
|
|
60
64
|
),
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../scripts/build/transpile/react-shim.js", "../../src/DSReadMore.tsx"],
|
|
4
|
-
"sourcesContent": ["import * as React from 'react';\nexport { React };\n", "import { ScreenReaderOnly } from '@elliemae/ds-accessibility';\nimport { describe, useGetXstyledProps, useMemoMergePropsWithDefault } from '@elliemae/ds-props-helpers';\nimport { DSTooltipV3 } from '@elliemae/ds-tooltip-v3';\nimport { useCallback } from 'react';\nimport { MoreLessButton } from './MoreLessButton.js';\nimport { DSReadMoreDataTestIds, DSReadMoreName } from './constants/index.js';\nimport { DSReadMorePropTypesSchema, defaultProps, type DSReadMoreT } from './react-desc-prop-types.js';\nimport { StyledTextContent, StyledTextWrapper, StyledTooltipWrapper } from './styled.js';\nimport { useReadMoreTruncate } from './useReadMoreTruncate.js';\n\nconst DSReadMore = (props: DSReadMoreT.Props) => {\n const propsWithDefault = useMemoMergePropsWithDefault<DSReadMoreT.InternalProps>(props, defaultProps);\n const { lines, more, less, content, withTooltip, ellipsis, onMore, onLess } = propsWithDefault;\n\n const xstyledProps = useGetXstyledProps(propsWithDefault);\n\n const { textWrapperRef, textRef, showButton, expanded, setExpanded, textProps, srOnlyText } =\n useReadMoreTruncate(propsWithDefault);\n\n const toggleExpanded = useCallback(\n (newExpanded: boolean) => {\n setExpanded(newExpanded);\n if (newExpanded) {\n onMore();\n } else {\n onLess();\n }\n },\n [onMore, onLess, setExpanded],\n );\n\n const getOwnerProps = useCallback(() => propsWithDefault, [propsWithDefault]);\n const getOwnerPropsArguments = useCallback(() => ({ expanded }), [expanded]);\n const getReadMoreDescribedByText = useCallback(() => {\n if (withTooltip) {\n return 'shows the full text for sighted users but the full text is already there for screen readers';\n }\n return `${expanded ? 'collapse' : 'expand'} the full text for sighted users but the full text is already there for screen readers`;\n }, [expanded, withTooltip]);\n\n return (\n <StyledTextWrapper\n innerRef={textWrapperRef}\n lines={lines}\n expanded={expanded}\n getOwnerProps={getOwnerProps}\n getOwnerPropsArguments={getOwnerPropsArguments}\n {...xstyledProps}\n >\n {srOnlyText}\n {/**\n * Hidden from screen readers unless referenced by `aria-describedby`\n * Ensures the text is only read when associated with a control\n * Prevents it from being focusable or announced independently\n */}\n <ScreenReaderOnly id=\"ds-read_more-text-disclaimer\" aria-hidden>\n {getReadMoreDescribedByText()}\n </ScreenReaderOnly>\n\n <StyledTextContent\n
|
|
5
|
-
"mappings": "AAAA,YAAY,WAAW;
|
|
4
|
+
"sourcesContent": ["import * as React from 'react';\nexport { React };\n", "import { ScreenReaderOnly } from '@elliemae/ds-accessibility';\nimport { describe, useGetXstyledProps, useMemoMergePropsWithDefault } from '@elliemae/ds-props-helpers';\nimport { DSTooltipV3 } from '@elliemae/ds-tooltip-v3';\nimport { useCallback, useMemo } from 'react';\nimport { MoreLessButton } from './MoreLessButton.js';\nimport { DSReadMoreDataTestIds, DSReadMoreName } from './constants/index.js';\nimport { DSReadMorePropTypesSchema, defaultProps, type DSReadMoreT } from './react-desc-prop-types.js';\nimport { StyledTextContent, StyledTextWrapper, StyledTooltipWrapper } from './styled.js';\nimport { useReadMoreTruncate } from './useReadMoreTruncate.js';\nimport { isSafari } from './utils.js';\n\nconst DSReadMore = (props: DSReadMoreT.Props) => {\n const propsWithDefault = useMemoMergePropsWithDefault<DSReadMoreT.InternalProps>(props, defaultProps);\n const { lines, more, less, content, withTooltip, ellipsis, onMore, onLess } = propsWithDefault;\n\n const xstyledProps = useGetXstyledProps(propsWithDefault);\n\n const { textWrapperRef, textRef, showButton, expanded, setExpanded, textProps, srOnlyText } =\n useReadMoreTruncate(propsWithDefault);\n\n const toggleExpanded = useCallback(\n (newExpanded: boolean) => {\n setExpanded(newExpanded);\n if (newExpanded) {\n onMore();\n } else {\n onLess();\n }\n },\n [onMore, onLess, setExpanded],\n );\n\n const getOwnerProps = useCallback(() => propsWithDefault, [propsWithDefault]);\n const getOwnerPropsArguments = useCallback(() => ({ expanded }), [expanded]);\n const getReadMoreDescribedByText = useCallback(() => {\n if (withTooltip) {\n return 'shows the full text for sighted users but the full text is already there for screen readers';\n }\n return `${expanded ? 'collapse' : 'expand'} the full text for sighted users but the full text is already there for screen readers`;\n }, [expanded, withTooltip]);\n\n const isSafariBrowser = useMemo(() => isSafari(), []);\n\n return (\n <StyledTextWrapper\n innerRef={textWrapperRef}\n lines={lines}\n expanded={expanded}\n getOwnerProps={getOwnerProps}\n getOwnerPropsArguments={getOwnerPropsArguments}\n isSafari={isSafariBrowser}\n {...xstyledProps}\n >\n {srOnlyText}\n {/**\n * Hidden from screen readers unless referenced by `aria-describedby`\n * Ensures the text is only read when associated with a control\n * Prevents it from being focusable or announced independently\n */}\n <ScreenReaderOnly id=\"ds-read_more-text-disclaimer\" aria-hidden>\n {getReadMoreDescribedByText()}\n </ScreenReaderOnly>\n\n <StyledTextContent\n innerRef={textRef}\n data-testid=\"ds-read_more-text\"\n {...textProps}\n getOwnerProps={getOwnerProps}\n getOwnerPropsArguments={getOwnerPropsArguments}\n lines={lines}\n expanded={expanded}\n isSafari={isSafariBrowser}\n >\n {content}\n </StyledTextContent>\n\n {showButton && !withTooltip && (\n <MoreLessButton\n expanded={expanded}\n label={expanded ? less : more}\n onClick={() => toggleExpanded(!expanded)}\n ellipsis={ellipsis}\n dataTestId={DSReadMoreDataTestIds.READ_MORE_BUTTON}\n getOwnerProps={getOwnerProps}\n getOwnerPropsArguments={getOwnerPropsArguments}\n />\n )}\n\n {withTooltip && showButton && (\n <StyledTooltipWrapper getOwnerProps={getOwnerProps} getOwnerPropsArguments={getOwnerPropsArguments}>\n <DSTooltipV3 text={content} wrapWords>\n <MoreLessButton\n expanded={expanded}\n label=\"...\"\n ellipsis={ellipsis}\n withTooltip={withTooltip}\n dataTestId={DSReadMoreDataTestIds.READ_MORE_TOOLTIP_BUTTON}\n getOwnerProps={getOwnerProps}\n getOwnerPropsArguments={getOwnerPropsArguments}\n />\n </DSTooltipV3>\n </StyledTooltipWrapper>\n )}\n </StyledTextWrapper>\n );\n};\n\nDSReadMore.displayName = DSReadMoreName;\nconst DSReadMoreWithSchema = describe(DSReadMore);\nDSReadMoreWithSchema.propTypes = DSReadMorePropTypesSchema;\n\nexport { DSReadMore, DSReadMoreWithSchema };\nexport default DSReadMore;\n"],
|
|
5
|
+
"mappings": "AAAA,YAAY,WAAW;AC4CnB,SAeE,KAfF;AA5CJ,SAAS,wBAAwB;AACjC,SAAS,UAAU,oBAAoB,oCAAoC;AAC3E,SAAS,mBAAmB;AAC5B,SAAS,aAAa,eAAe;AACrC,SAAS,sBAAsB;AAC/B,SAAS,uBAAuB,sBAAsB;AACtD,SAAS,2BAA2B,oBAAsC;AAC1E,SAAS,mBAAmB,mBAAmB,4BAA4B;AAC3E,SAAS,2BAA2B;AACpC,SAAS,gBAAgB;AAEzB,MAAM,aAAa,CAAC,UAA6B;AAC/C,QAAM,mBAAmB,6BAAwD,OAAO,YAAY;AACpG,QAAM,EAAE,OAAO,MAAM,MAAM,SAAS,aAAa,UAAU,QAAQ,OAAO,IAAI;AAE9E,QAAM,eAAe,mBAAmB,gBAAgB;AAExD,QAAM,EAAE,gBAAgB,SAAS,YAAY,UAAU,aAAa,WAAW,WAAW,IACxF,oBAAoB,gBAAgB;AAEtC,QAAM,iBAAiB;AAAA,IACrB,CAAC,gBAAyB;AACxB,kBAAY,WAAW;AACvB,UAAI,aAAa;AACf,eAAO;AAAA,MACT,OAAO;AACL,eAAO;AAAA,MACT;AAAA,IACF;AAAA,IACA,CAAC,QAAQ,QAAQ,WAAW;AAAA,EAC9B;AAEA,QAAM,gBAAgB,YAAY,MAAM,kBAAkB,CAAC,gBAAgB,CAAC;AAC5E,QAAM,yBAAyB,YAAY,OAAO,EAAE,SAAS,IAAI,CAAC,QAAQ,CAAC;AAC3E,QAAM,6BAA6B,YAAY,MAAM;AACnD,QAAI,aAAa;AACf,aAAO;AAAA,IACT;AACA,WAAO,GAAG,WAAW,aAAa,QAAQ;AAAA,EAC5C,GAAG,CAAC,UAAU,WAAW,CAAC;AAE1B,QAAM,kBAAkB,QAAQ,MAAM,SAAS,GAAG,CAAC,CAAC;AAEpD,SACE;AAAA,IAAC;AAAA;AAAA,MACC,UAAU;AAAA,MACV;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA,UAAU;AAAA,MACT,GAAG;AAAA,MAEH;AAAA;AAAA,QAMD,oBAAC,oBAAiB,IAAG,gCAA+B,eAAW,MAC5D,qCAA2B,GAC9B;AAAA,QAEA;AAAA,UAAC;AAAA;AAAA,YACC,UAAU;AAAA,YACV,eAAY;AAAA,YACX,GAAG;AAAA,YACJ;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA,YACA,UAAU;AAAA,YAET;AAAA;AAAA,QACH;AAAA,QAEC,cAAc,CAAC,eACd;AAAA,UAAC;AAAA;AAAA,YACC;AAAA,YACA,OAAO,WAAW,OAAO;AAAA,YACzB,SAAS,MAAM,eAAe,CAAC,QAAQ;AAAA,YACvC;AAAA,YACA,YAAY,sBAAsB;AAAA,YAClC;AAAA,YACA;AAAA;AAAA,QACF;AAAA,QAGD,eAAe,cACd,oBAAC,wBAAqB,eAA8B,wBAClD,8BAAC,eAAY,MAAM,SAAS,WAAS,MACnC;AAAA,UAAC;AAAA;AAAA,YACC;AAAA,YACA,OAAM;AAAA,YACN;AAAA,YACA;AAAA,YACA,YAAY,sBAAsB;AAAA,YAClC;AAAA,YACA;AAAA;AAAA,QACF,GACF,GACF;AAAA;AAAA;AAAA,EAEJ;AAEJ;AAEA,WAAW,cAAc;AACzB,MAAM,uBAAuB,SAAS,UAAU;AAChD,qBAAqB,YAAY;AAGjC,IAAO,qBAAQ;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -3,23 +3,31 @@ import { jsx, jsxs } from "react/jsx-runtime";
|
|
|
3
3
|
import { StyledButton, StyledButtonWrapper } from "./styled.js";
|
|
4
4
|
const MoreLessButton = (props) => {
|
|
5
5
|
const { expanded, label, onClick, ellipsis, withTooltip, dataTestId, getOwnerProps, getOwnerPropsArguments } = props;
|
|
6
|
-
return /* @__PURE__ */ jsxs(
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
6
|
+
return /* @__PURE__ */ jsxs(
|
|
7
|
+
StyledButtonWrapper,
|
|
8
|
+
{
|
|
9
|
+
expanded,
|
|
10
|
+
getOwnerProps,
|
|
11
|
+
getOwnerPropsArguments,
|
|
12
|
+
children: [
|
|
13
|
+
/* @__PURE__ */ jsx("span", { "aria-hidden": true, children: expanded || withTooltip ? " " : ellipsis }),
|
|
14
|
+
/* @__PURE__ */ jsx(
|
|
15
|
+
StyledButton,
|
|
16
|
+
{
|
|
17
|
+
withTooltip,
|
|
18
|
+
buttonType: "text",
|
|
19
|
+
onClick,
|
|
20
|
+
size: "s",
|
|
21
|
+
"data-testid": dataTestId,
|
|
22
|
+
getOwnerProps,
|
|
23
|
+
getOwnerPropsArguments,
|
|
24
|
+
"aria-describedby": "ds-read_more-text-disclaimer",
|
|
25
|
+
children: label
|
|
26
|
+
}
|
|
27
|
+
)
|
|
28
|
+
]
|
|
29
|
+
}
|
|
30
|
+
);
|
|
23
31
|
};
|
|
24
32
|
export {
|
|
25
33
|
MoreLessButton
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../scripts/build/transpile/react-shim.js", "../../src/MoreLessButton.tsx"],
|
|
4
|
-
"sourcesContent": ["import * as React from 'react';\nexport { React };\n", "import React from 'react';\nimport { StyledButton, StyledButtonWrapper } from './styled.js';\n\ninterface MoreLessButtonProps {\n expanded: boolean;\n label: string;\n onClick?: () => void;\n ellipsis: string;\n withTooltip?: boolean;\n dataTestId: string;\n getOwnerProps: () => object;\n getOwnerPropsArguments: () => object;\n}\n\nconst MoreLessButton = (props: MoreLessButtonProps) => {\n const { expanded, label, onClick, ellipsis, withTooltip, dataTestId, getOwnerProps, getOwnerPropsArguments } = props;\n\n return (\n <StyledButtonWrapper
|
|
5
|
-
"mappings": "AAAA,YAAY,WAAW;ACkBnB,
|
|
4
|
+
"sourcesContent": ["import * as React from 'react';\nexport { React };\n", "import React from 'react';\nimport { StyledButton, StyledButtonWrapper } from './styled.js';\n\ninterface MoreLessButtonProps {\n expanded: boolean;\n label: string;\n onClick?: () => void;\n ellipsis: string;\n withTooltip?: boolean;\n dataTestId: string;\n getOwnerProps: () => object;\n getOwnerPropsArguments: () => object;\n}\n\nconst MoreLessButton = (props: MoreLessButtonProps) => {\n const { expanded, label, onClick, ellipsis, withTooltip, dataTestId, getOwnerProps, getOwnerPropsArguments } = props;\n\n return (\n <StyledButtonWrapper\n expanded={expanded}\n getOwnerProps={getOwnerProps}\n getOwnerPropsArguments={getOwnerPropsArguments}\n >\n <span aria-hidden>{expanded || withTooltip ? ' ' : ellipsis}</span>\n\n <StyledButton\n withTooltip={withTooltip}\n buttonType=\"text\"\n onClick={onClick}\n size=\"s\"\n data-testid={dataTestId}\n getOwnerProps={getOwnerProps}\n getOwnerPropsArguments={getOwnerPropsArguments}\n aria-describedby=\"ds-read_more-text-disclaimer\"\n >\n {label}\n </StyledButton>\n </StyledButtonWrapper>\n );\n};\n\nexport { MoreLessButton };\n"],
|
|
5
|
+
"mappings": "AAAA,YAAY,WAAW;ACkBnB,SAKE,KALF;AAjBJ,SAAS,cAAc,2BAA2B;AAalD,MAAM,iBAAiB,CAAC,UAA+B;AACrD,QAAM,EAAE,UAAU,OAAO,SAAS,UAAU,aAAa,YAAY,eAAe,uBAAuB,IAAI;AAE/G,SACE;AAAA,IAAC;AAAA;AAAA,MACC;AAAA,MACA;AAAA,MACA;AAAA,MAEA;AAAA,4BAAC,UAAK,eAAW,MAAE,sBAAY,cAAc,MAAM,UAAS;AAAA,QAE5D;AAAA,UAAC;AAAA;AAAA,YACC;AAAA,YACA,YAAW;AAAA,YACX;AAAA,YACA,MAAK;AAAA,YACL,eAAa;AAAA,YACb;AAAA,YACA;AAAA,YACA,oBAAiB;AAAA,YAEhB;AAAA;AAAA,QACH;AAAA;AAAA;AAAA,EACF;AAEJ;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/dist/esm/package.json
CHANGED
package/dist/esm/styled.js
CHANGED
|
@@ -13,10 +13,9 @@ const StyledTextWrapper = styled("span", { name: DSReadMoreName, slot: DSReadMor
|
|
|
13
13
|
* - Avoids -webkit-line-clamp: 1 which breaks layout and can cause infinite ResizeObserver loops.
|
|
14
14
|
* - Instead, uses flex + white-space + overflow hidden for single-line truncation.
|
|
15
15
|
*/
|
|
16
|
-
${({ lines, expanded }) => lines === 1 && !expanded ? `
|
|
16
|
+
${({ lines, isSafari, expanded }) => lines === 1 && isSafari && !expanded ? `
|
|
17
17
|
display: flex;
|
|
18
18
|
align-items: baseline;
|
|
19
|
-
overflow: hidden;
|
|
20
19
|
white-space: nowrap;
|
|
21
20
|
` : `
|
|
22
21
|
display: -webkit-box;
|
|
@@ -33,9 +32,9 @@ const StyledTextWrapper = styled("span", { name: DSReadMoreName, slot: DSReadMor
|
|
|
33
32
|
${xStyledCommonProps}
|
|
34
33
|
`;
|
|
35
34
|
const StyledTextContent = styled("span", { name: DSReadMoreName, slot: DSReadMoreSlots.TEXT_CONTENT })`
|
|
36
|
-
${({ lines, expanded }) => lines === 1 && !expanded ? `
|
|
35
|
+
${({ lines, expanded, isSafari }) => lines === 1 && isSafari && !expanded ? `
|
|
37
36
|
flex: 1 1 auto;
|
|
38
|
-
overflow:
|
|
37
|
+
overflow-x: clip;
|
|
39
38
|
white-space: nowrap;
|
|
40
39
|
text-overflow: clip;
|
|
41
40
|
` : ""}
|
|
@@ -43,9 +42,14 @@ const StyledTextContent = styled("span", { name: DSReadMoreName, slot: DSReadMor
|
|
|
43
42
|
const StyledTooltipWrapper = styled("div", { name: DSReadMoreName, slot: DSReadMoreSlots.TOOLTIP_WRAPPER })`
|
|
44
43
|
display: inline-block;
|
|
45
44
|
`;
|
|
46
|
-
const StyledButtonWrapper = styled("span", { name: DSReadMoreName, slot: DSReadMoreSlots.BUTTON_WRAPPER })
|
|
45
|
+
const StyledButtonWrapper = styled("span", { name: DSReadMoreName, slot: DSReadMoreSlots.BUTTON_WRAPPER })`
|
|
46
|
+
flex: none;
|
|
47
|
+
display: -webkit-inline-box;
|
|
48
|
+
-webkit-line-clamp: 2;
|
|
49
|
+
-webkit-box-orient: vertical;
|
|
50
|
+
`;
|
|
47
51
|
const StyledButton = styled(DSButtonV3, { name: DSReadMoreName, slot: DSReadMoreSlots.BUTTON })`
|
|
48
|
-
padding: 0;
|
|
52
|
+
padding: ${({ theme }) => `0 ${theme.space.xxxs}`};
|
|
49
53
|
color: ${({ withTooltip, theme }) => withTooltip ? "inherit" : `${theme.colors.brand["600"]}`};
|
|
50
54
|
border: 0;
|
|
51
55
|
margin: 0;
|
package/dist/esm/styled.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../scripts/build/transpile/react-shim.js", "../../src/styled.tsx"],
|
|
4
|
-
"sourcesContent": ["import * as React from 'react';\nexport { React };\n", "import { styled, xStyledCommonProps } from '@elliemae/ds-system';\nimport { DSButtonV3 } from '@elliemae/ds-button-v2';\nimport { DSReadMoreName, DSReadMoreSlots } from './constants/index.js';\n\nexport const StyledTextWrapper = styled('span', { name: DSReadMoreName, slot: DSReadMoreSlots.ROOT })<{\n lines: number;\n expanded: boolean;\n}>`\n position: relative;\n /**\n * PUI-16110 - [ReadMore] Autotriggered-Visually broken in safari + Sequoia\n * -----------------------------------\n * In Safari 17.4+ (macOS Sequoia), using scrollHeight or offsetHeight with line-clamp\n * can break the layout and, potentially, trigger infinite loops through ResizeObserver.\n *\n * - Avoids -webkit-line-clamp: 1 which breaks layout and can cause infinite ResizeObserver loops.\n * - Instead, uses flex + white-space + overflow hidden for single-line truncation.\n */\n ${({ lines, expanded }) =>\n lines === 1 && !expanded\n ? `\n display: flex;\n align-items: baseline;\n
|
|
5
|
-
"mappings": "AAAA,YAAY,WAAW;ACAvB,SAAS,QAAQ,0BAA0B;AAC3C,SAAS,kBAAkB;AAC3B,SAAS,gBAAgB,uBAAuB;AAEzC,MAAM,oBAAoB,OAAO,QAAQ,EAAE,MAAM,gBAAgB,MAAM,gBAAgB,KAAK,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,
|
|
4
|
+
"sourcesContent": ["import * as React from 'react';\nexport { React };\n", "import { styled, xStyledCommonProps } from '@elliemae/ds-system';\nimport { DSButtonV3 } from '@elliemae/ds-button-v2';\nimport { DSReadMoreName, DSReadMoreSlots } from './constants/index.js';\n\nexport const StyledTextWrapper = styled('span', { name: DSReadMoreName, slot: DSReadMoreSlots.ROOT })<{\n lines: number;\n expanded: boolean;\n isSafari: boolean;\n}>`\n position: relative;\n /**\n * PUI-16110 - [ReadMore] Autotriggered-Visually broken in safari + Sequoia\n * -----------------------------------\n * In Safari 17.4+ (macOS Sequoia), using scrollHeight or offsetHeight with line-clamp\n * can break the layout and, potentially, trigger infinite loops through ResizeObserver.\n *\n * - Avoids -webkit-line-clamp: 1 which breaks layout and can cause infinite ResizeObserver loops.\n * - Instead, uses flex + white-space + overflow hidden for single-line truncation.\n */\n ${({ lines, isSafari, expanded }) =>\n lines === 1 && isSafari && !expanded\n ? `\n display: flex;\n align-items: baseline;\n white-space: nowrap;\n `\n : `\n display: -webkit-box;\n -webkit-line-clamp: ${expanded ? 'unset' : lines};\n -webkit-box-orient: vertical;\n `}\n\n -webkit-hyphens: auto;\n -moz-hyphens: auto;\n -ms-hyphens: auto;\n hyphens: auto;\n overflow-wrap: break-word;\n white-space: pre-wrap;\n ${xStyledCommonProps}\n`;\n\nexport const StyledTextContent = styled('span', { name: DSReadMoreName, slot: DSReadMoreSlots.TEXT_CONTENT })<{\n lines: number;\n expanded: boolean;\n isSafari: boolean;\n}>`\n ${({ lines, expanded, isSafari }) =>\n lines === 1 && isSafari && !expanded\n ? `\n flex: 1 1 auto;\n overflow-x: clip;\n white-space: nowrap;\n text-overflow: clip;\n `\n : ''}\n`;\n\nexport const StyledTooltipWrapper = styled('div', { name: DSReadMoreName, slot: DSReadMoreSlots.TOOLTIP_WRAPPER })`\n display: inline-block;\n`;\n\nexport const StyledButtonWrapper = styled('span', { name: DSReadMoreName, slot: DSReadMoreSlots.BUTTON_WRAPPER })<{\n expanded: boolean;\n}>`\n flex: none;\n display: -webkit-inline-box;\n -webkit-line-clamp: 2;\n -webkit-box-orient: vertical;\n`;\n\nexport const StyledButton = styled(DSButtonV3, { name: DSReadMoreName, slot: DSReadMoreSlots.BUTTON })<{\n withTooltip?: boolean;\n}>`\n padding: ${({ theme }) => `0 ${theme.space.xxxs}`};\n color: ${({ withTooltip, theme }) => (withTooltip ? 'inherit' : `${theme.colors.brand['600']}`)};\n border: 0;\n margin: 0;\n font-size: 12px;\n min-width: 0px;\n height: auto;\n text-transform: unset;\n &:hover:not([disabled]) {\n color: ${({ withTooltip, theme }) => (withTooltip ? 'inherit' : `${theme.colors.brand['600']}`)};\n background-color: transparent;\n text-decoration: ${({ withTooltip }) => (withTooltip ? '' : 'underline')};\n }\n`;\n\nexport const ScreenReaderOnly = styled.span`\n clip: rect(0 0 0 0);\n clip-path: inset(50%);\n height: 100%;\n overflow: hidden;\n position: absolute;\n width: 100%;\n`;\n"],
|
|
5
|
+
"mappings": "AAAA,YAAY,WAAW;ACAvB,SAAS,QAAQ,0BAA0B;AAC3C,SAAS,kBAAkB;AAC3B,SAAS,gBAAgB,uBAAuB;AAEzC,MAAM,oBAAoB,OAAO,QAAQ,EAAE,MAAM,gBAAgB,MAAM,gBAAgB,KAAK,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAehG,CAAC,EAAE,OAAO,UAAU,SAAS,MAC7B,UAAU,KAAK,YAAY,CAAC,WACxB;AAAA;AAAA;AAAA;AAAA,MAKA;AAAA;AAAA,0BAEkB,WAAW,UAAU,KAAK;AAAA;AAAA,GAEjD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAQC,kBAAkB;AAAA;AAGf,MAAM,oBAAoB,OAAO,QAAQ,EAAE,MAAM,gBAAgB,MAAM,gBAAgB,aAAa,CAAC;AAAA,IAKxG,CAAC,EAAE,OAAO,UAAU,SAAS,MAC7B,UAAU,KAAK,YAAY,CAAC,WACxB;AAAA;AAAA;AAAA;AAAA;AAAA,MAMA,EAAE;AAAA;AAGH,MAAM,uBAAuB,OAAO,OAAO,EAAE,MAAM,gBAAgB,MAAM,gBAAgB,gBAAgB,CAAC;AAAA;AAAA;AAI1G,MAAM,sBAAsB,OAAO,QAAQ,EAAE,MAAM,gBAAgB,MAAM,gBAAgB,eAAe,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA;AASzG,MAAM,eAAe,OAAO,YAAY,EAAE,MAAM,gBAAgB,MAAM,gBAAgB,OAAO,CAAC;AAAA,aAGxF,CAAC,EAAE,MAAM,MAAM,KAAK,MAAM,MAAM,IAAI,EAAE;AAAA,WACxC,CAAC,EAAE,aAAa,MAAM,MAAO,cAAc,YAAY,GAAG,MAAM,OAAO,MAAM,KAAK,CAAC,EAAG;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,aAQpF,CAAC,EAAE,aAAa,MAAM,MAAO,cAAc,YAAY,GAAG,MAAM,OAAO,MAAM,KAAK,CAAC,EAAG;AAAA;AAAA,uBAE5E,CAAC,EAAE,YAAY,MAAO,cAAc,KAAK,WAAY;AAAA;AAAA;AAIrE,MAAM,mBAAmB,OAAO;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -3,6 +3,7 @@ import { jsx } from "react/jsx-runtime";
|
|
|
3
3
|
import { useLayoutEffect, useMemo, useRef, useState } from "react";
|
|
4
4
|
import { useOnElementResize } from "./useOnElementResize.js";
|
|
5
5
|
import { ScreenReaderOnly } from "./styled.js";
|
|
6
|
+
import { isSafari } from "./utils.js";
|
|
6
7
|
const overflows = ({ offsetHeight, scrollHeight }) => offsetHeight + 4 < scrollHeight;
|
|
7
8
|
const binSearchTextOverflow = (element, parent, content) => {
|
|
8
9
|
let left = 0;
|
|
@@ -32,7 +33,7 @@ const useReadMoreTruncate = ({ lines, content }) => {
|
|
|
32
33
|
const wrapperEl = textWrapperRef.current;
|
|
33
34
|
if (!textEl || !wrapperEl) return;
|
|
34
35
|
textEl.innerText = content;
|
|
35
|
-
if (lines === 1) {
|
|
36
|
+
if (lines === 1 && isSafari()) {
|
|
36
37
|
const computed = getComputedStyle(textEl);
|
|
37
38
|
const font = `${computed.fontWeight} ${computed.fontSize} ${computed.fontFamily}`;
|
|
38
39
|
const canvas = document.createElement("canvas");
|
|
@@ -54,7 +55,9 @@ const useReadMoreTruncate = ({ lines, content }) => {
|
|
|
54
55
|
if (!expanded && isOverflowing) {
|
|
55
56
|
const doBinaryTruncate = () => {
|
|
56
57
|
if (!textEl || !wrapperEl) return;
|
|
57
|
-
|
|
58
|
+
requestAnimationFrame(() => {
|
|
59
|
+
binSearchTextOverflow(textEl, wrapperEl, content);
|
|
60
|
+
});
|
|
58
61
|
};
|
|
59
62
|
if ("fonts" in document && document.fonts.ready) {
|
|
60
63
|
document.fonts.ready.then(() => {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../scripts/build/transpile/react-shim.js", "../../src/useReadMoreTruncate.tsx"],
|
|
4
|
-
"sourcesContent": ["import * as React from 'react';\nexport { React };\n", "/* eslint-disable @typescript-eslint/no-misused-promises */\n/* eslint-disable max-statements */\n/* eslint-disable complexity */\n// useReadMoreTruncate.tsx\nimport React, { useLayoutEffect, useMemo, useRef, useState } from 'react';\nimport { useOnElementResize } from './useOnElementResize.js';\nimport type { DSReadMoreT } from './react-desc-prop-types.js';\nimport { ScreenReaderOnly } from './styled.js';\n\n// offsetHeight + margin/buffer to avoid subpixel errors\nconst overflows = ({ offsetHeight, scrollHeight }: { offsetHeight: number; scrollHeight: number }) =>\n offsetHeight + 4 < scrollHeight;\n\n/**\n * binSearchTextOverflow slices the content until there's no overflow.\n * It assumes `element` is the <span> node containing the text, and `parent`\n * is the wrapper with the height/clamp applied.\n */\nconst binSearchTextOverflow = (element: HTMLElement, parent: HTMLElement, content: string) => {\n let left = 0;\n let right = content.length;\n let textEnd = 0;\n\n // Place the full text before starting\n element.innerText = content;\n\n while (left <= right) {\n const mid = Math.floor((left + right) / 2);\n element.innerText = content.slice(0, mid);\n\n if (overflows(parent)) {\n // Still overflowing \u2192 reduce\n right = mid - 1;\n } else {\n // No overflow \u2192 we can increase\n textEnd = mid;\n left = mid + 1;\n }\n }\n\n element.innerText = content.slice(0, textEnd);\n};\n\nexport const useReadMoreTruncate = ({ lines, content }: Pick<DSReadMoreT.InternalProps, 'lines' | 'content'>) => {\n const textWrapperRef = useRef<HTMLSpanElement>(null);\n const textRef = useRef<HTMLSpanElement>(null);\n\n const [expanded, setExpanded] = useState(false);\n const [showButton, setShowButton] = useState(false);\n\n // Detect container size changes\n const { width, height } = useOnElementResize(textWrapperRef);\n\n useLayoutEffect(() => {\n const textEl = textRef.current;\n const wrapperEl = textWrapperRef.current;\n if (!textEl || !wrapperEl) return;\n\n // 1) Always reset the text to \"content\"\n textEl.innerText = content;\n\n // 2) Case lines === 1: measure with canvas\n if (lines === 1) {\n const computed = getComputedStyle(textEl);\n const font = `${computed.fontWeight} ${computed.fontSize} ${computed.fontFamily}`;\n\n const canvas = document.createElement('canvas');\n const ctx = canvas.getContext('2d');\n let truncated = false;\n\n if (ctx) {\n ctx.font = font;\n const measuredWidth = ctx.measureText(content).width;\n truncated = measuredWidth > wrapperEl.clientWidth;\n }\n\n setShowButton(expanded || truncated);\n // No need to truncate char by char in single-line: hiding with CSS is enough.\n return;\n }\n\n // 3) For multiline: first check native overflow\n const isOverflowing = overflows({\n offsetHeight: wrapperEl.offsetHeight,\n scrollHeight: wrapperEl.scrollHeight,\n });\n setShowButton(expanded || isOverflowing);\n\n // 4) If not expanded and there is overflow, adjust the slice with binary search\n if (!expanded && isOverflowing) {\n // Function that executes binSearchTextOverflow\n const doBinaryTruncate = () => {\n if (!textEl || !wrapperEl) return;\n binSearchTextOverflow(textEl, wrapperEl, content);\n };\n\n // If the browser supports document.fonts, wait for them to load.\n if ('fonts' in document && document.fonts.ready) {\n document.fonts.ready\n .then(() => {\n doBinaryTruncate();\n })\n .catch(() => {\n // If fonts.ready fails, fall back to timeout\n setTimeout(doBinaryTruncate, 0);\n });\n } else {\n // Fallback for browsers without the Font Loading API\n setTimeout(doBinaryTruncate, 0);\n }\n }\n }, [lines, content, expanded, width, height]);\n\n // Hidden text for screen readers (always the full string)\n const srOnlyText = useMemo(() => <ScreenReaderOnly>{content}</ScreenReaderOnly>, [content]);\n\n // Mark the visible content as aria-hidden\n const textProps = useMemo(() => ({ 'aria-hidden': true }), []);\n\n return {\n textWrapperRef,\n textRef,\n showButton,\n expanded,\n setExpanded,\n textProps,\n srOnlyText,\n };\n};\n"],
|
|
5
|
-
"mappings": "AAAA,YAAY,WAAW;
|
|
4
|
+
"sourcesContent": ["import * as React from 'react';\nexport { React };\n", "/* eslint-disable @typescript-eslint/no-misused-promises */\n/* eslint-disable max-statements */\n/* eslint-disable complexity */\n// useReadMoreTruncate.tsx\nimport React, { useLayoutEffect, useMemo, useRef, useState } from 'react';\nimport { useOnElementResize } from './useOnElementResize.js';\nimport type { DSReadMoreT } from './react-desc-prop-types.js';\nimport { ScreenReaderOnly } from './styled.js';\nimport { isSafari } from './utils.js';\n\n// offsetHeight + margin/buffer to avoid subpixel errors\nconst overflows = ({ offsetHeight, scrollHeight }: { offsetHeight: number; scrollHeight: number }) =>\n offsetHeight + 4 < scrollHeight;\n\n/**\n * binSearchTextOverflow slices the content until there's no overflow.\n * It assumes `element` is the <span> node containing the text, and `parent`\n * is the wrapper with the height/clamp applied.\n */\nconst binSearchTextOverflow = (element: HTMLElement, parent: HTMLElement, content: string) => {\n let left = 0;\n let right = content.length;\n let textEnd = 0;\n\n // Place the full text before starting\n element.innerText = content;\n\n while (left <= right) {\n const mid = Math.floor((left + right) / 2);\n element.innerText = content.slice(0, mid);\n\n if (overflows(parent)) {\n // Still overflowing \u2192 reduce\n right = mid - 1;\n } else {\n // No overflow \u2192 we can increase\n textEnd = mid;\n left = mid + 1;\n }\n }\n\n element.innerText = content.slice(0, textEnd);\n};\n\nexport const useReadMoreTruncate = ({ lines, content }: Pick<DSReadMoreT.InternalProps, 'lines' | 'content'>) => {\n const textWrapperRef = useRef<HTMLSpanElement>(null);\n const textRef = useRef<HTMLSpanElement>(null);\n\n const [expanded, setExpanded] = useState(false);\n const [showButton, setShowButton] = useState(false);\n\n // Detect container size changes\n const { width, height } = useOnElementResize(textWrapperRef);\n\n useLayoutEffect(() => {\n const textEl = textRef.current;\n const wrapperEl = textWrapperRef.current;\n if (!textEl || !wrapperEl) return;\n\n // 1) Always reset the text to \"content\"\n textEl.innerText = content;\n\n // 2) Case lines === 1: measure with canvas\n if (lines === 1 && isSafari()) {\n const computed = getComputedStyle(textEl);\n const font = `${computed.fontWeight} ${computed.fontSize} ${computed.fontFamily}`;\n\n const canvas = document.createElement('canvas');\n const ctx = canvas.getContext('2d');\n let truncated = false;\n\n if (ctx) {\n ctx.font = font;\n const measuredWidth = ctx.measureText(content).width;\n truncated = measuredWidth > wrapperEl.clientWidth;\n }\n\n setShowButton(expanded || truncated);\n // No need to truncate char by char in single-line: hiding with CSS is enough.\n return;\n }\n\n // 3) For multiline: first check native overflow\n const isOverflowing = overflows({\n offsetHeight: wrapperEl.offsetHeight,\n scrollHeight: wrapperEl.scrollHeight,\n });\n setShowButton(expanded || isOverflowing);\n\n // 4) If not expanded and there is overflow, adjust the slice with binary search\n if (!expanded && isOverflowing) {\n // Function that executes binSearchTextOverflow\n const doBinaryTruncate = () => {\n if (!textEl || !wrapperEl) return;\n requestAnimationFrame(() => {\n binSearchTextOverflow(textEl, wrapperEl, content);\n });\n };\n\n // If the browser supports document.fonts, wait for them to load.\n if ('fonts' in document && document.fonts.ready) {\n document.fonts.ready\n .then(() => {\n doBinaryTruncate();\n })\n .catch(() => {\n // If fonts.ready fails, fall back to timeout\n setTimeout(doBinaryTruncate, 0);\n });\n } else {\n // Fallback for browsers without the Font Loading API\n setTimeout(doBinaryTruncate, 0);\n }\n }\n }, [lines, content, expanded, width, height]);\n\n // Hidden text for screen readers (always the full string)\n const srOnlyText = useMemo(() => <ScreenReaderOnly>{content}</ScreenReaderOnly>, [content]);\n\n // Mark the visible content as aria-hidden\n const textProps = useMemo(() => ({ 'aria-hidden': true }), []);\n\n return {\n textWrapperRef,\n textRef,\n showButton,\n expanded,\n setExpanded,\n textProps,\n srOnlyText,\n };\n};\n"],
|
|
5
|
+
"mappings": "AAAA,YAAY,WAAW;ACqHY;AAjHnC,SAAgB,iBAAiB,SAAS,QAAQ,gBAAgB;AAClE,SAAS,0BAA0B;AAEnC,SAAS,wBAAwB;AACjC,SAAS,gBAAgB;AAGzB,MAAM,YAAY,CAAC,EAAE,cAAc,aAAa,MAC9C,eAAe,IAAI;AAOrB,MAAM,wBAAwB,CAAC,SAAsB,QAAqB,YAAoB;AAC5F,MAAI,OAAO;AACX,MAAI,QAAQ,QAAQ;AACpB,MAAI,UAAU;AAGd,UAAQ,YAAY;AAEpB,SAAO,QAAQ,OAAO;AACpB,UAAM,MAAM,KAAK,OAAO,OAAO,SAAS,CAAC;AACzC,YAAQ,YAAY,QAAQ,MAAM,GAAG,GAAG;AAExC,QAAI,UAAU,MAAM,GAAG;AAErB,cAAQ,MAAM;AAAA,IAChB,OAAO;AAEL,gBAAU;AACV,aAAO,MAAM;AAAA,IACf;AAAA,EACF;AAEA,UAAQ,YAAY,QAAQ,MAAM,GAAG,OAAO;AAC9C;AAEO,MAAM,sBAAsB,CAAC,EAAE,OAAO,QAAQ,MAA4D;AAC/G,QAAM,iBAAiB,OAAwB,IAAI;AACnD,QAAM,UAAU,OAAwB,IAAI;AAE5C,QAAM,CAAC,UAAU,WAAW,IAAI,SAAS,KAAK;AAC9C,QAAM,CAAC,YAAY,aAAa,IAAI,SAAS,KAAK;AAGlD,QAAM,EAAE,OAAO,OAAO,IAAI,mBAAmB,cAAc;AAE3D,kBAAgB,MAAM;AACpB,UAAM,SAAS,QAAQ;AACvB,UAAM,YAAY,eAAe;AACjC,QAAI,CAAC,UAAU,CAAC,UAAW;AAG3B,WAAO,YAAY;AAGnB,QAAI,UAAU,KAAK,SAAS,GAAG;AAC7B,YAAM,WAAW,iBAAiB,MAAM;AACxC,YAAM,OAAO,GAAG,SAAS,UAAU,IAAI,SAAS,QAAQ,IAAI,SAAS,UAAU;AAE/E,YAAM,SAAS,SAAS,cAAc,QAAQ;AAC9C,YAAM,MAAM,OAAO,WAAW,IAAI;AAClC,UAAI,YAAY;AAEhB,UAAI,KAAK;AACP,YAAI,OAAO;AACX,cAAM,gBAAgB,IAAI,YAAY,OAAO,EAAE;AAC/C,oBAAY,gBAAgB,UAAU;AAAA,MACxC;AAEA,oBAAc,YAAY,SAAS;AAEnC;AAAA,IACF;AAGA,UAAM,gBAAgB,UAAU;AAAA,MAC9B,cAAc,UAAU;AAAA,MACxB,cAAc,UAAU;AAAA,IAC1B,CAAC;AACD,kBAAc,YAAY,aAAa;AAGvC,QAAI,CAAC,YAAY,eAAe;AAE9B,YAAM,mBAAmB,MAAM;AAC7B,YAAI,CAAC,UAAU,CAAC,UAAW;AAC3B,8BAAsB,MAAM;AAC1B,gCAAsB,QAAQ,WAAW,OAAO;AAAA,QAClD,CAAC;AAAA,MACH;AAGA,UAAI,WAAW,YAAY,SAAS,MAAM,OAAO;AAC/C,iBAAS,MAAM,MACZ,KAAK,MAAM;AACV,2BAAiB;AAAA,QACnB,CAAC,EACA,MAAM,MAAM;AAEX,qBAAW,kBAAkB,CAAC;AAAA,QAChC,CAAC;AAAA,MACL,OAAO;AAEL,mBAAW,kBAAkB,CAAC;AAAA,MAChC;AAAA,IACF;AAAA,EACF,GAAG,CAAC,OAAO,SAAS,UAAU,OAAO,MAAM,CAAC;AAG5C,QAAM,aAAa,QAAQ,MAAM,oBAAC,oBAAkB,mBAAQ,GAAqB,CAAC,OAAO,CAAC;AAG1F,QAAM,YAAY,QAAQ,OAAO,EAAE,eAAe,KAAK,IAAI,CAAC,CAAC;AAE7D,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
const isSafari = () => {
|
|
3
|
+
if (typeof navigator === "undefined") return false;
|
|
4
|
+
const ua = navigator.userAgent;
|
|
5
|
+
return /safari/i.test(ua) && !/chrome|chromium|android/i.test(ua);
|
|
6
|
+
};
|
|
7
|
+
export {
|
|
8
|
+
isSafari
|
|
9
|
+
};
|
|
10
|
+
//# sourceMappingURL=utils.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../../scripts/build/transpile/react-shim.js", "../../src/utils.ts"],
|
|
4
|
+
"sourcesContent": ["import * as React from 'react';\nexport { React };\n", "/**\n * Detects whether the current browser is Safari (excluding Chrome/Chromium and Android browsers).\n *\n * This utility is used to apply Safari-specific layout and styling workarounds.\n * Safari has known differences and bugs around text truncation, line-clamp,\n * and layout measurements (e.g. offsetHeight/scrollHeight), which can affect\n * components that rely on precise DOM sizing.\n *\n * The check is intentionally lightweight and user-agent based, as feature\n * detection is not reliable for these Safari-specific rendering issues.\n *\n * When used inside React components, this function should be evaluated once\n * (e.g. via useMemo) to avoid layout churn and focus instability caused by\n * re-evaluating browser detection on every render.\n */\nexport const isSafari = () => {\n if (typeof navigator === 'undefined') return false;\n\n const ua = navigator.userAgent;\n // Safari but not Chrome/Chromium/Android\n return /safari/i.test(ua) && !/chrome|chromium|android/i.test(ua);\n};\n"],
|
|
5
|
+
"mappings": "AAAA,YAAY,WAAW;ACehB,MAAM,WAAW,MAAM;AAC5B,MAAI,OAAO,cAAc,YAAa,QAAO;AAE7C,QAAM,KAAK,UAAU;AAErB,SAAO,UAAU,KAAK,EAAE,KAAK,CAAC,2BAA2B,KAAK,EAAE;AAClE;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
package/dist/types/styled.d.ts
CHANGED
|
@@ -1,13 +1,17 @@
|
|
|
1
1
|
export declare const StyledTextWrapper: import("styled-components").StyledComponent<"span", import("@elliemae/ds-system").Theme, {
|
|
2
2
|
lines: number;
|
|
3
3
|
expanded: boolean;
|
|
4
|
+
isSafari: boolean;
|
|
4
5
|
} & import("@elliemae/ds-system").OwnerInterface & import("@elliemae/ds-system").InnerRefInterface<"span">, never>;
|
|
5
6
|
export declare const StyledTextContent: import("styled-components").StyledComponent<"span", import("@elliemae/ds-system").Theme, {
|
|
6
7
|
lines: number;
|
|
7
8
|
expanded: boolean;
|
|
9
|
+
isSafari: boolean;
|
|
8
10
|
} & import("@elliemae/ds-system").OwnerInterface & import("@elliemae/ds-system").InnerRefInterface<"span">, never>;
|
|
9
11
|
export declare const StyledTooltipWrapper: import("styled-components").StyledComponent<"div", import("@elliemae/ds-system").Theme, object & import("@elliemae/ds-system").OwnerInterface & import("@elliemae/ds-system").InnerRefInterface<"div">, never>;
|
|
10
|
-
export declare const StyledButtonWrapper: import("styled-components").StyledComponent<"span", import("@elliemae/ds-system").Theme,
|
|
12
|
+
export declare const StyledButtonWrapper: import("styled-components").StyledComponent<"span", import("@elliemae/ds-system").Theme, {
|
|
13
|
+
expanded: boolean;
|
|
14
|
+
} & import("@elliemae/ds-system").OwnerInterface & import("@elliemae/ds-system").InnerRefInterface<"span">, never>;
|
|
11
15
|
export declare const StyledButton: import("styled-components").StyledComponent<import("react").ComponentType<import("@elliemae/ds-button-v2").DSButtonV3T.Props>, import("@elliemae/ds-system").Theme, {
|
|
12
16
|
withTooltip?: boolean;
|
|
13
17
|
} & import("@elliemae/ds-system").OwnerInterface & import("@elliemae/ds-system").InnerRefInterface<import("react").ComponentType<import("@elliemae/ds-button-v2").DSButtonV3T.Props>>, never>;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Detects whether the current browser is Safari (excluding Chrome/Chromium and Android browsers).
|
|
3
|
+
*
|
|
4
|
+
* This utility is used to apply Safari-specific layout and styling workarounds.
|
|
5
|
+
* Safari has known differences and bugs around text truncation, line-clamp,
|
|
6
|
+
* and layout measurements (e.g. offsetHeight/scrollHeight), which can affect
|
|
7
|
+
* components that rely on precise DOM sizing.
|
|
8
|
+
*
|
|
9
|
+
* The check is intentionally lightweight and user-agent based, as feature
|
|
10
|
+
* detection is not reliable for these Safari-specific rendering issues.
|
|
11
|
+
*
|
|
12
|
+
* When used inside React components, this function should be evaluated once
|
|
13
|
+
* (e.g. via useMemo) to avoid layout churn and focus instability caused by
|
|
14
|
+
* re-evaluating browser detection on every render.
|
|
15
|
+
*/
|
|
16
|
+
export declare const isSafari: () => boolean;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@elliemae/ds-read-more",
|
|
3
|
-
"version": "3.57.0-next.
|
|
3
|
+
"version": "3.57.0-next.50",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"description": "ICE MT - Dimsum - Read More",
|
|
6
6
|
"files": [
|
|
@@ -35,37 +35,38 @@
|
|
|
35
35
|
"reportFile": "tests.xml",
|
|
36
36
|
"indent": 4
|
|
37
37
|
},
|
|
38
|
+
"scripts": {
|
|
39
|
+
"dev": "cross-env NODE_ENV=development node ../../../scripts/build/build.mjs --watch",
|
|
40
|
+
"test": "playwright test -c ./playwright.config.mjs && pui-cli test --passWithNoTests --coverage=\"false\"",
|
|
41
|
+
"lint": "node ../../../scripts/lint.mjs --fix",
|
|
42
|
+
"lint:strict": "node ../../../scripts/lint-strict.mjs",
|
|
43
|
+
"dts": "node ../../../scripts/dts.mjs",
|
|
44
|
+
"build": "cross-env NODE_ENV=production node ../../../scripts/build/build.mjs",
|
|
45
|
+
"checkDeps": "npm exec ../../util/ds-codemods -- check-missing-packages --projectFolderPath=\"./\" --ignorePackagesGlobPattern=\"\" --ignoreFilesGlobPattern=\"**/test-ables/*,**/tests/*\""
|
|
46
|
+
},
|
|
38
47
|
"dependencies": {
|
|
39
|
-
"@elliemae/ds-accessibility": "3.57.0-next.
|
|
40
|
-
"@elliemae/ds-button-v2": "3.57.0-next.
|
|
41
|
-
"@elliemae/ds-
|
|
42
|
-
"@elliemae/ds-
|
|
43
|
-
"@elliemae/ds-tooltip-v3": "3.57.0-next.
|
|
48
|
+
"@elliemae/ds-accessibility": "3.57.0-next.50",
|
|
49
|
+
"@elliemae/ds-button-v2": "3.57.0-next.50",
|
|
50
|
+
"@elliemae/ds-props-helpers": "3.57.0-next.50",
|
|
51
|
+
"@elliemae/ds-system": "3.57.0-next.50",
|
|
52
|
+
"@elliemae/ds-tooltip-v3": "3.57.0-next.50"
|
|
44
53
|
},
|
|
45
54
|
"devDependencies": {
|
|
46
|
-
"@elliemae/
|
|
47
|
-
"
|
|
48
|
-
"
|
|
49
|
-
"
|
|
50
|
-
"
|
|
55
|
+
"@elliemae/ds-monorepo-devops": "3.57.0-next.50",
|
|
56
|
+
"@elliemae/ds-test-utils": "3.57.0-next.50",
|
|
57
|
+
"@elliemae/pui-cli": "catalog:",
|
|
58
|
+
"jest": "catalog:",
|
|
59
|
+
"styled-components": "catalog:"
|
|
51
60
|
},
|
|
52
61
|
"peerDependencies": {
|
|
53
|
-
"lodash-es": "
|
|
54
|
-
"react": "
|
|
55
|
-
"react-dom": "
|
|
56
|
-
"styled-components": "
|
|
62
|
+
"lodash-es": "catalog:",
|
|
63
|
+
"react": "catalog:",
|
|
64
|
+
"react-dom": "catalog:",
|
|
65
|
+
"styled-components": "catalog:"
|
|
57
66
|
},
|
|
58
67
|
"publishConfig": {
|
|
59
68
|
"access": "public",
|
|
60
69
|
"typeSafety": true
|
|
61
70
|
},
|
|
62
|
-
"
|
|
63
|
-
|
|
64
|
-
"test": "pui-cli test --passWithNoTests --coverage=\"false\"",
|
|
65
|
-
"lint": "node ../../../scripts/lint.mjs --fix",
|
|
66
|
-
"lint:strict": "node ../../../scripts/lint-strict.mjs",
|
|
67
|
-
"dts": "node ../../../scripts/dts.mjs",
|
|
68
|
-
"build": "cross-env NODE_ENV=production node ../../../scripts/build/build.mjs",
|
|
69
|
-
"checkDeps": "npm exec ../../util/ds-codemods -- check-missing-packages --projectFolderPath=\"./\" --ignorePackagesGlobPattern=\"\" --ignoreFilesGlobPattern=\"**/test-ables/*,**/tests/*\""
|
|
70
|
-
}
|
|
71
|
-
}
|
|
71
|
+
"gitHead": "990bc67e15cecbb87e61ba44977d9d00de40aad5"
|
|
72
|
+
}
|