@pie-lib/text-select 1.32.1 → 1.33.0-mui-update.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/utils.js CHANGED
@@ -4,64 +4,48 @@ Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
6
  exports.prepareText = exports.parseParagraphs = exports.parseParagraph = exports.parseBrs = void 0;
7
-
8
7
  var createElementFromHTML = function createElementFromHTML() {
9
8
  var htmlString = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
10
9
  var div = document.createElement('div');
11
10
  div.innerHTML = htmlString.trim();
12
11
  return div;
13
12
  };
14
-
15
- var parseBrs = function parseBrs(dom) {
13
+ var parseBrs = exports.parseBrs = function parseBrs(dom) {
16
14
  var brs = dom.querySelectorAll('br');
17
15
  brs.forEach(function (br) {
18
16
  return br.replaceWith('\n');
19
17
  });
20
18
  dom.innerHTML = dom.innerHTML.replace(/\n\n/g, '\n');
21
19
  };
22
-
23
- exports.parseBrs = parseBrs;
24
-
25
- var parseParagraph = function parseParagraph(paragraph, end) {
20
+ var parseParagraph = exports.parseParagraph = function parseParagraph(paragraph, end) {
26
21
  if (end) {
27
22
  return paragraph.innerHTML;
28
23
  }
29
-
30
24
  return "".concat(paragraph.innerHTML, "\n\n");
31
25
  };
32
-
33
- exports.parseParagraph = parseParagraph;
34
-
35
- var parseParagraphs = function parseParagraphs(dom) {
36
- var paragraphs = dom.querySelectorAll('p'); // separate variable for easily debugging, if needed
37
-
26
+ var parseParagraphs = exports.parseParagraphs = function parseParagraphs(dom) {
27
+ var paragraphs = dom.querySelectorAll('p');
28
+ // separate variable for easily debugging, if needed
38
29
  var str = '';
39
30
  paragraphs.forEach(function (par, index) {
40
31
  str += parseParagraph(par, index === paragraphs.length - 1);
41
32
  });
42
33
  return str || null;
43
34
  };
44
-
45
- exports.parseParagraphs = parseParagraphs;
46
-
47
- var prepareText = function prepareText(text) {
35
+ var prepareText = exports.prepareText = function prepareText(text) {
48
36
  var txtDom = createElementFromHTML(text);
49
37
  var allDomElements = Array.from(txtDom.querySelectorAll('*'));
50
-
51
38
  if (txtDom.querySelectorAll('p').length === 0) {
52
39
  var div = document.createElement('div');
53
40
  div.innerHTML = "<p>".concat(txtDom.innerHTML, "</p>");
54
41
  txtDom = div;
55
- } // if no dom elements, we just return the text
56
-
42
+ }
57
43
 
44
+ // if no dom elements, we just return the text
58
45
  if (allDomElements.length === 0) {
59
46
  return text;
60
47
  }
61
-
62
48
  parseBrs(txtDom);
63
49
  return parseParagraphs(txtDom);
64
50
  };
65
-
66
- exports.prepareText = prepareText;
67
51
  //# sourceMappingURL=utils.js.map
package/lib/utils.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/utils.js"],"names":["createElementFromHTML","htmlString","div","document","createElement","innerHTML","trim","parseBrs","dom","brs","querySelectorAll","forEach","br","replaceWith","replace","parseParagraph","paragraph","end","parseParagraphs","paragraphs","str","par","index","length","prepareText","text","txtDom","allDomElements","Array","from"],"mappings":";;;;;;;AAAA,IAAMA,qBAAqB,GAAG,SAAxBA,qBAAwB,GAAqB;AAAA,MAApBC,UAAoB,uEAAP,EAAO;AACjD,MAAMC,GAAG,GAAGC,QAAQ,CAACC,aAAT,CAAuB,KAAvB,CAAZ;AAEAF,EAAAA,GAAG,CAACG,SAAJ,GAAgBJ,UAAU,CAACK,IAAX,EAAhB;AAEA,SAAOJ,GAAP;AACD,CAND;;AAQO,IAAMK,QAAQ,GAAG,SAAXA,QAAW,CAACC,GAAD,EAAS;AAC/B,MAAMC,GAAG,GAAGD,GAAG,CAACE,gBAAJ,CAAqB,IAArB,CAAZ;AAEAD,EAAAA,GAAG,CAACE,OAAJ,CAAY,UAACC,EAAD;AAAA,WAAQA,EAAE,CAACC,WAAH,CAAe,IAAf,CAAR;AAAA,GAAZ;AAEAL,EAAAA,GAAG,CAACH,SAAJ,GAAgBG,GAAG,CAACH,SAAJ,CAAcS,OAAd,CAAsB,OAAtB,EAA+B,IAA/B,CAAhB;AACD,CANM;;;;AAQA,IAAMC,cAAc,GAAG,SAAjBA,cAAiB,CAACC,SAAD,EAAYC,GAAZ,EAAoB;AAChD,MAAIA,GAAJ,EAAS;AACP,WAAOD,SAAS,CAACX,SAAjB;AACD;;AAED,mBAAUW,SAAS,CAACX,SAApB;AACD,CANM;;;;AAQA,IAAMa,eAAe,GAAG,SAAlBA,eAAkB,CAACV,GAAD,EAAS;AACtC,MAAMW,UAAU,GAAGX,GAAG,CAACE,gBAAJ,CAAqB,GAArB,CAAnB,CADsC,CAEtC;;AACA,MAAIU,GAAG,GAAG,EAAV;AAEAD,EAAAA,UAAU,CAACR,OAAX,CAAmB,UAACU,GAAD,EAAMC,KAAN,EAAgB;AACjCF,IAAAA,GAAG,IAAIL,cAAc,CAACM,GAAD,EAAMC,KAAK,KAAKH,UAAU,CAACI,MAAX,GAAoB,CAApC,CAArB;AACD,GAFD;AAIA,SAAOH,GAAG,IAAI,IAAd;AACD,CAVM;;;;AAYA,IAAMI,WAAW,GAAG,SAAdA,WAAc,CAACC,IAAD,EAAU;AACnC,MAAIC,MAAM,GAAG1B,qBAAqB,CAACyB,IAAD,CAAlC;AACA,MAAME,cAAc,GAAGC,KAAK,CAACC,IAAN,CAAWH,MAAM,CAAChB,gBAAP,CAAwB,GAAxB,CAAX,CAAvB;;AAEA,MAAIgB,MAAM,CAAChB,gBAAP,CAAwB,GAAxB,EAA6Ba,MAA7B,KAAwC,CAA5C,EAA+C;AAC7C,QAAMrB,GAAG,GAAGC,QAAQ,CAACC,aAAT,CAAuB,KAAvB,CAAZ;AAEAF,IAAAA,GAAG,CAACG,SAAJ,gBAAsBqB,MAAM,CAACrB,SAA7B;AACAqB,IAAAA,MAAM,GAAGxB,GAAT;AACD,GATkC,CAWnC;;;AACA,MAAIyB,cAAc,CAACJ,MAAf,KAA0B,CAA9B,EAAiC;AAC/B,WAAOE,IAAP;AACD;;AAEDlB,EAAAA,QAAQ,CAACmB,MAAD,CAAR;AAEA,SAAOR,eAAe,CAACQ,MAAD,CAAtB;AACD,CAnBM","sourcesContent":["const createElementFromHTML = (htmlString = '') => {\n const div = document.createElement('div');\n\n div.innerHTML = htmlString.trim();\n\n return div;\n};\n\nexport const parseBrs = (dom) => {\n const brs = dom.querySelectorAll('br');\n\n brs.forEach((br) => br.replaceWith('\\n'));\n\n dom.innerHTML = dom.innerHTML.replace(/\\n\\n/g, '\\n');\n};\n\nexport const parseParagraph = (paragraph, end) => {\n if (end) {\n return paragraph.innerHTML;\n }\n\n return `${paragraph.innerHTML}\\n\\n`;\n};\n\nexport const parseParagraphs = (dom) => {\n const paragraphs = dom.querySelectorAll('p');\n // separate variable for easily debugging, if needed\n let str = '';\n\n paragraphs.forEach((par, index) => {\n str += parseParagraph(par, index === paragraphs.length - 1);\n });\n\n return str || null;\n};\n\nexport const prepareText = (text) => {\n let txtDom = createElementFromHTML(text);\n const allDomElements = Array.from(txtDom.querySelectorAll('*'));\n\n if (txtDom.querySelectorAll('p').length === 0) {\n const div = document.createElement('div');\n\n div.innerHTML = `<p>${txtDom.innerHTML}</p>`;\n txtDom = div;\n }\n\n // if no dom elements, we just return the text\n if (allDomElements.length === 0) {\n return text;\n }\n\n parseBrs(txtDom);\n\n return parseParagraphs(txtDom);\n};\n"],"file":"utils.js"}
1
+ {"version":3,"file":"utils.js","names":["createElementFromHTML","htmlString","arguments","length","undefined","div","document","createElement","innerHTML","trim","parseBrs","exports","dom","brs","querySelectorAll","forEach","br","replaceWith","replace","parseParagraph","paragraph","end","concat","parseParagraphs","paragraphs","str","par","index","prepareText","text","txtDom","allDomElements","Array","from"],"sources":["../src/utils.js"],"sourcesContent":["const createElementFromHTML = (htmlString = '') => {\n const div = document.createElement('div');\n\n div.innerHTML = htmlString.trim();\n\n return div;\n};\n\nexport const parseBrs = (dom) => {\n const brs = dom.querySelectorAll('br');\n\n brs.forEach((br) => br.replaceWith('\\n'));\n\n dom.innerHTML = dom.innerHTML.replace(/\\n\\n/g, '\\n');\n};\n\nexport const parseParagraph = (paragraph, end) => {\n if (end) {\n return paragraph.innerHTML;\n }\n\n return `${paragraph.innerHTML}\\n\\n`;\n};\n\nexport const parseParagraphs = (dom) => {\n const paragraphs = dom.querySelectorAll('p');\n // separate variable for easily debugging, if needed\n let str = '';\n\n paragraphs.forEach((par, index) => {\n str += parseParagraph(par, index === paragraphs.length - 1);\n });\n\n return str || null;\n};\n\nexport const prepareText = (text) => {\n let txtDom = createElementFromHTML(text);\n const allDomElements = Array.from(txtDom.querySelectorAll('*'));\n\n if (txtDom.querySelectorAll('p').length === 0) {\n const div = document.createElement('div');\n\n div.innerHTML = `<p>${txtDom.innerHTML}</p>`;\n txtDom = div;\n }\n\n // if no dom elements, we just return the text\n if (allDomElements.length === 0) {\n return text;\n }\n\n parseBrs(txtDom);\n\n return parseParagraphs(txtDom);\n};\n"],"mappings":";;;;;;AAAA,IAAMA,qBAAqB,GAAG,SAAxBA,qBAAqBA,CAAA,EAAwB;EAAA,IAApBC,UAAU,GAAAC,SAAA,CAAAC,MAAA,QAAAD,SAAA,QAAAE,SAAA,GAAAF,SAAA,MAAG,EAAE;EAC5C,IAAMG,GAAG,GAAGC,QAAQ,CAACC,aAAa,CAAC,KAAK,CAAC;EAEzCF,GAAG,CAACG,SAAS,GAAGP,UAAU,CAACQ,IAAI,CAAC,CAAC;EAEjC,OAAOJ,GAAG;AACZ,CAAC;AAEM,IAAMK,QAAQ,GAAAC,OAAA,CAAAD,QAAA,GAAG,SAAXA,QAAQA,CAAIE,GAAG,EAAK;EAC/B,IAAMC,GAAG,GAAGD,GAAG,CAACE,gBAAgB,CAAC,IAAI,CAAC;EAEtCD,GAAG,CAACE,OAAO,CAAC,UAACC,EAAE;IAAA,OAAKA,EAAE,CAACC,WAAW,CAAC,IAAI,CAAC;EAAA,EAAC;EAEzCL,GAAG,CAACJ,SAAS,GAAGI,GAAG,CAACJ,SAAS,CAACU,OAAO,CAAC,OAAO,EAAE,IAAI,CAAC;AACtD,CAAC;AAEM,IAAMC,cAAc,GAAAR,OAAA,CAAAQ,cAAA,GAAG,SAAjBA,cAAcA,CAAIC,SAAS,EAAEC,GAAG,EAAK;EAChD,IAAIA,GAAG,EAAE;IACP,OAAOD,SAAS,CAACZ,SAAS;EAC5B;EAEA,UAAAc,MAAA,CAAUF,SAAS,CAACZ,SAAS;AAC/B,CAAC;AAEM,IAAMe,eAAe,GAAAZ,OAAA,CAAAY,eAAA,GAAG,SAAlBA,eAAeA,CAAIX,GAAG,EAAK;EACtC,IAAMY,UAAU,GAAGZ,GAAG,CAACE,gBAAgB,CAAC,GAAG,CAAC;EAC5C;EACA,IAAIW,GAAG,GAAG,EAAE;EAEZD,UAAU,CAACT,OAAO,CAAC,UAACW,GAAG,EAAEC,KAAK,EAAK;IACjCF,GAAG,IAAIN,cAAc,CAACO,GAAG,EAAEC,KAAK,KAAKH,UAAU,CAACrB,MAAM,GAAG,CAAC,CAAC;EAC7D,CAAC,CAAC;EAEF,OAAOsB,GAAG,IAAI,IAAI;AACpB,CAAC;AAEM,IAAMG,WAAW,GAAAjB,OAAA,CAAAiB,WAAA,GAAG,SAAdA,WAAWA,CAAIC,IAAI,EAAK;EACnC,IAAIC,MAAM,GAAG9B,qBAAqB,CAAC6B,IAAI,CAAC;EACxC,IAAME,cAAc,GAAGC,KAAK,CAACC,IAAI,CAACH,MAAM,CAAChB,gBAAgB,CAAC,GAAG,CAAC,CAAC;EAE/D,IAAIgB,MAAM,CAAChB,gBAAgB,CAAC,GAAG,CAAC,CAACX,MAAM,KAAK,CAAC,EAAE;IAC7C,IAAME,GAAG,GAAGC,QAAQ,CAACC,aAAa,CAAC,KAAK,CAAC;IAEzCF,GAAG,CAACG,SAAS,SAAAc,MAAA,CAASQ,MAAM,CAACtB,SAAS,SAAM;IAC5CsB,MAAM,GAAGzB,GAAG;EACd;;EAEA;EACA,IAAI0B,cAAc,CAAC5B,MAAM,KAAK,CAAC,EAAE;IAC/B,OAAO0B,IAAI;EACb;EAEAnB,QAAQ,CAACoB,MAAM,CAAC;EAEhB,OAAOP,eAAe,CAACO,MAAM,CAAC;AAChC,CAAC","ignoreList":[]}
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "1.32.1",
6
+ "version": "1.33.0-mui-update.0",
7
7
  "description": "Some react components for text selection",
8
8
  "keywords": [
9
9
  "react",
@@ -17,11 +17,14 @@
17
17
  "author": "",
18
18
  "license": "ISC",
19
19
  "dependencies": {
20
- "@material-ui/icons": "^3.0.2",
20
+ "@emotion/react": "^11.14.0",
21
+ "@emotion/style": "^0.8.0",
22
+ "@mui/icons-material": "^7.3.4",
23
+ "@mui/material": "^7.3.4",
21
24
  "@pie-framework/parse-english": "^1.0.0",
22
- "@pie-lib/render-ui": "^4.35.1",
23
- "@pie-lib/style-utils": "^0.21.1",
24
- "@pie-lib/translator": "^2.23.1",
25
+ "@pie-lib/render-ui": "^4.36.0-mui-update.0",
26
+ "@pie-lib/style-utils": "^0.22.0-mui-update.0",
27
+ "@pie-lib/translator": "^2.25.0-mui-update.0",
25
28
  "classnames": "^2.2.6",
26
29
  "debug": "^4.1.1",
27
30
  "invariant": "^2.2.4",
@@ -30,17 +33,15 @@
30
33
  "unist-util-inspect": "^4.1.3"
31
34
  },
32
35
  "devDependencies": {
33
- "@material-ui/core": "^3.8.3",
34
36
  "@pie-framework/parse-english": "^1.0.0",
35
- "@pie-lib/test-utils": "^0.22.1",
37
+ "@pie-lib/test-utils": "^0.23.0-mui-update.0",
36
38
  "natural": "^0.6.3",
37
- "react": "^16.8.1",
38
- "react-dom": "^16.9.0"
39
+ "react": "^18.2.0",
40
+ "react-dom": "^18.2.0"
39
41
  },
40
42
  "peerDependencies": {
41
- "@material-ui/core": "^3.8.3",
42
- "react": "^16.8.1"
43
+ "react": "^18.2.0"
43
44
  },
44
- "gitHead": "ce0ec7134200c51f3a3301257be3e707054b36ca",
45
+ "gitHead": "2e0222bda045f46b504640a972d43787e68288c4",
45
46
  "scripts": {}
46
47
  }
package/src/legend.js CHANGED
@@ -1,80 +1,87 @@
1
1
  import React from 'react';
2
- import { withStyles } from '@material-ui/core/styles';
3
- import Check from '@material-ui/icons/Check';
4
- import Close from '@material-ui/icons/Close';
2
+ import { styled } from '@mui/material/styles';
3
+ import Check from '@mui/icons-material/Check';
4
+ import Close from '@mui/icons-material/Close';
5
5
  import { color } from '@pie-lib/render-ui';
6
6
  import Translator from '@pie-lib/translator';
7
- import classNames from 'classnames';
8
7
 
9
8
  const { translator } = Translator;
10
9
 
11
- export const Legend = withStyles((theme) => ({
12
- flexContainer: {
13
- display: 'flex',
14
- flexDirection: 'row',
15
- alignItems: 'center',
16
- gap: `${2 * theme.spacing.unit}px`,
17
- borderBottom: '1px solid lightgrey',
18
- borderTop: '1px solid lightgrey',
19
- paddingBottom: theme.spacing.unit,
20
- paddingTop: theme.spacing.unit,
21
- marginBottom: theme.spacing.unit,
22
- },
23
- key: {
24
- fontSize: '14px',
25
- fontWeight: 'bold',
26
- color: color.black(),
27
- marginLeft: theme.spacing.unit,
28
- },
29
- container: {
30
- position: 'relative',
31
- padding: '4px',
32
- fontSize: '14px',
33
- borderRadius: '4px',
34
- },
35
- correct: {
36
- border: `${color.correctTertiary()} solid 2px`,
37
- },
38
- incorrect: {
39
- border: `${color.incorrectWithIcon()} solid 2px`,
40
- },
41
- missing: {
42
- border: `${color.incorrectWithIcon()} dashed 2px`,
43
- },
44
- incorrectIcon: {
45
- backgroundColor: color.incorrectWithIcon(),
46
- },
47
- correctIcon: {
48
- backgroundColor: color.correctTertiary(),
49
- },
50
- icon: {
51
- color: color.white(),
52
- position: 'absolute',
53
- top: '-8px',
54
- left: '-8px',
55
- borderRadius: '50%',
56
- fontSize: '12px',
57
- padding: '2px',
58
- },
59
- }))(({ classes, language, showOnlyCorrect }) => {
10
+ const StyledFlexContainer = styled('div')(({ theme }) => ({
11
+ display: 'flex',
12
+ flexDirection: 'row',
13
+ alignItems: 'center',
14
+ gap: theme.spacing(2),
15
+ borderBottom: '1px solid lightgrey',
16
+ borderTop: '1px solid lightgrey',
17
+ paddingBottom: theme.spacing(1),
18
+ paddingTop: theme.spacing(1),
19
+ marginBottom: theme.spacing(1),
20
+ }));
21
+
22
+ const StyledKey = styled('span')(({ theme }) => ({
23
+ fontSize: '14px',
24
+ fontWeight: 'bold',
25
+ color: color.black(),
26
+ marginLeft: theme.spacing(1),
27
+ }));
28
+
29
+ const StyledContainer = styled('div')(() => ({
30
+ position: 'relative',
31
+ padding: '4px',
32
+ fontSize: '14px',
33
+ borderRadius: '4px',
34
+ }));
35
+
36
+ const StyledCorrectContainer = styled(StyledContainer)(() => ({
37
+ border: `${color.correctTertiary()} solid 2px`,
38
+ }));
39
+
40
+ const StyledIncorrectContainer = styled(StyledContainer)(() => ({
41
+ border: `${color.incorrectWithIcon()} solid 2px`,
42
+ }));
43
+
44
+ const StyledMissingContainer = styled(StyledContainer)(() => ({
45
+ border: `${color.incorrectWithIcon()} dashed 2px`,
46
+ }));
47
+
48
+ const StyledIcon = styled('div')(() => ({
49
+ color: color.white(),
50
+ position: 'absolute',
51
+ top: '-8px',
52
+ left: '-8px',
53
+ borderRadius: '50%',
54
+ fontSize: '12px',
55
+ padding: '2px',
56
+ }));
57
+
58
+ const StyledCorrectIcon = styled(StyledIcon)(() => ({
59
+ backgroundColor: color.correctTertiary(),
60
+ }));
61
+
62
+ const StyledIncorrectIcon = styled(StyledIcon)(() => ({
63
+ backgroundColor: color.incorrectWithIcon(),
64
+ }));
65
+
66
+ export const Legend = ({ language, showOnlyCorrect }) => {
60
67
  const legendItems = [
61
68
  {
62
69
  Icon: Check,
63
70
  label: translator.t('selectText.correctAnswerSelected', { lng: language }),
64
- containerClass: classNames(classes.correct, classes.container),
65
- iconClass: classNames(classes.correctIcon, classes.icon),
71
+ Container: StyledCorrectContainer,
72
+ IconComponent: StyledCorrectIcon,
66
73
  },
67
74
  {
68
75
  Icon: Close,
69
76
  label: translator.t('selectText.incorrectSelection', { lng: language }),
70
- containerClass: classNames(classes.incorrect, classes.container),
71
- iconClass: classNames(classes.incorrectIcon, classes.icon),
77
+ Container: StyledIncorrectContainer,
78
+ IconComponent: StyledIncorrectIcon,
72
79
  },
73
80
  {
74
81
  Icon: Close,
75
82
  label: translator.t('selectText.correctAnswerNotSelected', { lng: language }),
76
- containerClass: classNames(classes.missing, classes.container),
77
- iconClass: classNames(classes.incorrectIcon, classes.icon),
83
+ Container: StyledMissingContainer,
84
+ IconComponent: StyledIncorrectIcon,
78
85
  },
79
86
  ];
80
87
 
@@ -83,14 +90,16 @@ export const Legend = withStyles((theme) => ({
83
90
  }
84
91
 
85
92
  return (
86
- <div className={classes.flexContainer}>
87
- <span className={classes.key}>{translator.t('selectText.key', { lng: language })}</span>
88
- {legendItems.map(({ Icon, label, containerClass, iconClass }, idx) => (
89
- <div key={idx} className={containerClass}>
90
- <Icon className={iconClass} />
93
+ <StyledFlexContainer>
94
+ <StyledKey>{translator.t('selectText.key', { lng: language })}</StyledKey>
95
+ {legendItems.map(({ Icon, label, Container, IconComponent }, idx) => (
96
+ <Container key={idx}>
97
+ <IconComponent>
98
+ <Icon />
99
+ </IconComponent>
91
100
  <span>{label}</span>
92
- </div>
101
+ </Container>
93
102
  ))}
94
- </div>
103
+ </StyledFlexContainer>
95
104
  );
96
- });
105
+ };
@@ -1,21 +1,28 @@
1
1
  import React from 'react';
2
2
  import PropTypes from 'prop-types';
3
3
  import Token, { TokenTypes } from './token';
4
- import { withStyles } from '@material-ui/core/styles';
5
- import classNames from 'classnames';
4
+ import { styled } from '@mui/material/styles';
6
5
  import clone from 'lodash/clone';
7
6
  import debug from 'debug';
8
7
  import { noSelect } from '@pie-lib/style-utils';
9
- import { renderToString } from 'react-dom/server';
10
8
  import isEqual from 'lodash/isEqual';
11
9
 
12
10
  const log = debug('@pie-lib:text-select:token-select');
13
11
 
12
+ const StyledTokenSelect = styled('div')(() => ({
13
+ backgroundColor: 'none',
14
+ whiteSpace: 'pre',
15
+ ...noSelect(),
16
+ '& p': {
17
+ whiteSpace: 'break-spaces',
18
+ margin: 0,
19
+ },
20
+ }));
21
+
14
22
  export class TokenSelect extends React.Component {
15
23
  static propTypes = {
16
24
  tokens: PropTypes.arrayOf(PropTypes.shape(TokenTypes)).isRequired,
17
25
  className: PropTypes.string,
18
- classes: PropTypes.object.isRequired,
19
26
  onChange: PropTypes.func.isRequired,
20
27
  disabled: PropTypes.bool,
21
28
  highlightChoices: PropTypes.bool,
@@ -34,49 +41,36 @@ export class TokenSelect extends React.Component {
34
41
  canSelectMore = (selectedCount) => {
35
42
  const { maxNoOfSelections } = this.props;
36
43
 
37
- if (maxNoOfSelections === 1) {
38
- return true;
39
- }
44
+ if (maxNoOfSelections === 1) return true;
40
45
 
41
46
  log('[canSelectMore] maxNoOfSelections: ', maxNoOfSelections, 'selectedCount: ', selectedCount);
42
47
  return maxNoOfSelections <= 0 || (isFinite(maxNoOfSelections) && selectedCount < maxNoOfSelections);
43
48
  };
44
49
 
45
- /**
46
- @function
47
- @param { object } event
48
-
49
- @description
50
- each token is wrapped into a span that has Token.rootClassName class and indexkey attribute (represents the index of the token)
51
- tokens are updated with the targeted token having the correct value set for 'selected' property
52
- */
53
50
  toggleToken = (event) => {
54
51
  const { target } = event;
55
52
  const { tokens, animationsDisabled } = this.props;
56
53
  const tokensCloned = clone(tokens);
57
- const targetSpanWrapper = target.closest(`.${Token.rootClassName}`);
58
- const targetedTokenIndex = targetSpanWrapper && targetSpanWrapper.dataset && targetSpanWrapper.dataset.indexkey;
59
- const t = targetedTokenIndex && tokensCloned[targetedTokenIndex];
60
54
 
61
- // don't toggle if we are in print mode, token correctness is defined or if it's missing
62
- // (missing means that it was evaluated as correct and not selected)
55
+ const targetSpanWrapper = target.closest?.(`.${Token.rootClassName}`);
56
+ const targetedTokenIndex = targetSpanWrapper?.dataset?.indexkey;
57
+ const t = targetedTokenIndex !== undefined ? tokensCloned[targetedTokenIndex] : undefined;
58
+
59
+ // don't toggle if in print mode, correctness is defined, or is missing
63
60
  if (t && t.correct === undefined && !animationsDisabled && !t.isMissing) {
64
61
  const { onChange, maxNoOfSelections } = this.props;
65
62
  const selected = !t.selected;
66
63
 
67
64
  if (maxNoOfSelections === 1 && this.selectedCount() === 1) {
68
- const selectedToken = (tokens || []).filter((t) => t.selected);
69
-
65
+ const selectedToken = (tokens || []).filter((tk) => tk.selected);
70
66
  const updatedTokens = tokensCloned.map((token) => {
71
67
  if (isEqual(token, selectedToken[0])) {
72
68
  return { ...token, selected: false };
73
69
  }
74
-
75
70
  return { ...token, selectable: true };
76
71
  });
77
72
 
78
- const update = { ...t, selected: !t.selected };
79
-
73
+ const update = { ...t, selected };
80
74
  updatedTokens.splice(targetedTokenIndex, 1, update);
81
75
  onChange(updatedTokens);
82
76
  } else {
@@ -84,32 +78,42 @@ export class TokenSelect extends React.Component {
84
78
  log('skip toggle max reached');
85
79
  return;
86
80
  }
87
-
88
- const update = { ...t, selected: !t.selected };
89
-
81
+ const update = { ...t, selected };
90
82
  tokensCloned.splice(targetedTokenIndex, 1, update);
91
83
  onChange(tokensCloned);
92
84
  }
93
85
  }
94
86
  };
95
87
 
96
- generateTokensInHtml = () => {
88
+ /** Build a React tree instead of an HTML string so Emotion can inject CSS */
89
+ generateTokensNodes = () => {
97
90
  const { tokens, disabled, highlightChoices, animationsDisabled } = this.props;
98
91
  const selectedCount = this.selectedCount();
92
+
99
93
  const isLineBreak = (text) => text === '\n';
100
94
  const isNewParagraph = (text) => text === '\n\n';
101
95
 
102
- const reducer = (accumulator, t, index) => {
96
+ const paragraphs = [];
97
+ let currentChildren = [];
98
+
99
+ const flushParagraph = () => {
100
+ // Always push a <p>, even if empty, to mirror previous behavior
101
+ paragraphs.push(<p key={`p-${paragraphs.length}`}>{currentChildren}</p>);
102
+ currentChildren = [];
103
+ };
104
+
105
+ (tokens || []).forEach((t, index) => {
103
106
  const selectable = t.selected || (t.selectable && this.canSelectMore(selectedCount));
104
107
  const showCorrectAnswer = t.correct !== undefined && (t.selectable || t.selected);
105
- let finalAcc = accumulator;
106
108
 
107
109
  if (isNewParagraph(t.text)) {
108
- return finalAcc + '</p><p>';
110
+ flushParagraph();
111
+ return;
109
112
  }
110
113
 
111
114
  if (isLineBreak(t.text)) {
112
- return finalAcc + '<br>';
115
+ currentChildren.push(<br key={`br-${index}`} />);
116
+ return;
113
117
  }
114
118
 
115
119
  if (
@@ -117,48 +121,41 @@ export class TokenSelect extends React.Component {
117
121
  showCorrectAnswer ||
118
122
  t.selected ||
119
123
  t.isMissing ||
120
- (animationsDisabled && t.predefined) // if we are in print mode
124
+ (animationsDisabled && t.predefined) // print mode
121
125
  ) {
122
- return (
123
- finalAcc +
124
- renderToString(
125
- <Token
126
- key={index}
127
- disabled={disabled}
128
- index={index}
129
- {...t}
130
- selectable={selectable}
131
- highlight={highlightChoices}
132
- animationsDisabled={animationsDisabled}
133
- />,
134
- )
126
+ currentChildren.push(
127
+ <Token
128
+ key={index}
129
+ disabled={disabled}
130
+ index={index}
131
+ {...t}
132
+ selectable={selectable}
133
+ highlight={highlightChoices}
134
+ animationsDisabled={animationsDisabled}
135
+ />
135
136
  );
136
137
  } else {
137
- return accumulator + t.text;
138
+ // raw text node – React will escape as needed
139
+ currentChildren.push(<React.Fragment key={index}>{t.text}</React.Fragment>);
138
140
  }
139
- };
141
+ });
140
142
 
141
- const reduceResult = (tokens || []).reduce(reducer, '<p>');
143
+ // flush last paragraph
144
+ flushParagraph();
142
145
 
143
- return reduceResult + '</p>';
146
+ return paragraphs;
144
147
  };
145
148
 
146
149
  render() {
147
- const { classes, className: classNameProp } = this.props;
148
- const className = classNames(classes.tokenSelect, classNameProp);
149
- const html = this.generateTokensInHtml();
150
-
151
- return <div className={className} dangerouslySetInnerHTML={{ __html: html }} onClick={this.toggleToken} />;
150
+ const { className: classNameProp } = this.props;
151
+ const nodes = this.generateTokensNodes();
152
+
153
+ return (
154
+ <StyledTokenSelect className={classNameProp} onClick={this.toggleToken}>
155
+ {nodes}
156
+ </StyledTokenSelect>
157
+ );
152
158
  }
153
159
  }
154
160
 
155
- export default withStyles(() => ({
156
- tokenSelect: {
157
- backgroundColor: 'none',
158
- whiteSpace: 'pre',
159
- ...noSelect(),
160
- '& p': {
161
- whiteSpace: 'break-spaces',
162
- },
163
- },
164
- }))(TokenSelect);
161
+ export default TokenSelect;