@pie-lib/render-ui 6.1.1-next.37 → 6.1.1-next.57

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.
Files changed (105) hide show
  1. package/CHANGELOG.json +1 -0
  2. package/CHANGELOG.md +1144 -0
  3. package/LICENSE.md +5 -0
  4. package/lib/assets/enableAudioAutoplayImage.js +8 -0
  5. package/lib/assets/enableAudioAutoplayImage.js.map +1 -0
  6. package/lib/collapsible/index.js +105 -0
  7. package/lib/collapsible/index.js.map +1 -0
  8. package/lib/color.js +235 -0
  9. package/lib/color.js.map +1 -0
  10. package/lib/feedback.js +112 -0
  11. package/lib/feedback.js.map +1 -0
  12. package/lib/has-media.js +22 -0
  13. package/lib/has-media.js.map +1 -0
  14. package/lib/has-text.js +22 -0
  15. package/lib/has-text.js.map +1 -0
  16. package/lib/html-and-math.js +54 -0
  17. package/lib/html-and-math.js.map +1 -0
  18. package/lib/index.js +113 -0
  19. package/lib/index.js.map +1 -0
  20. package/lib/input-container.js +59 -0
  21. package/lib/input-container.js.map +1 -0
  22. package/lib/preview-layout.js +65 -0
  23. package/lib/preview-layout.js.map +1 -0
  24. package/lib/preview-prompt.js +299 -0
  25. package/lib/preview-prompt.js.map +1 -0
  26. package/lib/purpose.js +22 -0
  27. package/lib/purpose.js.map +1 -0
  28. package/lib/readable.js +22 -0
  29. package/lib/readable.js.map +1 -0
  30. package/lib/response-indicators.js +102 -0
  31. package/lib/response-indicators.js.map +1 -0
  32. package/lib/transform-headings.js +53 -0
  33. package/lib/transform-headings.js.map +1 -0
  34. package/lib/ui-layout.js +125 -0
  35. package/lib/ui-layout.js.map +1 -0
  36. package/lib/withUndoReset.js +135 -0
  37. package/lib/withUndoReset.js.map +1 -0
  38. package/package.json +20 -32
  39. package/src/__tests__/color.test.js +259 -0
  40. package/src/__tests__/feedback.test.jsx +279 -0
  41. package/src/__tests__/has-media.test.js +19 -0
  42. package/src/__tests__/has-text.test.js +20 -0
  43. package/src/__tests__/html-and-math.test.js +36 -0
  44. package/src/__tests__/input-container.test.jsx +328 -0
  45. package/src/__tests__/preview-layout.test.jsx +349 -0
  46. package/src/__tests__/preview-prompt.test.jsx +379 -0
  47. package/src/__tests__/purpose.test.jsx +51 -0
  48. package/src/__tests__/readable.test.jsx +69 -0
  49. package/src/__tests__/response-indicators.test.jsx +111 -0
  50. package/src/__tests__/ui-layout.test.jsx +52 -0
  51. package/src/__tests__/withUndoReset.test.jsx +176 -0
  52. package/src/assets/enableAudioAutoplayImage.js +1 -0
  53. package/src/collapsible/__tests__/index.test.jsx +39 -0
  54. package/src/collapsible/index.jsx +64 -0
  55. package/src/color.js +139 -0
  56. package/src/feedback.jsx +84 -0
  57. package/src/has-media.js +16 -0
  58. package/src/has-text.js +18 -0
  59. package/src/html-and-math.js +21 -0
  60. package/src/index.js +35 -0
  61. package/src/input-container.jsx +47 -0
  62. package/src/preview-layout.jsx +41 -0
  63. package/src/preview-prompt.jsx +280 -0
  64. package/src/purpose.jsx +17 -0
  65. package/src/readable.jsx +19 -0
  66. package/src/response-indicators.jsx +82 -0
  67. package/{dist/transform-headings.d.ts → src/transform-headings.js} +30 -9
  68. package/src/ui-layout.jsx +96 -0
  69. package/src/withUndoReset.jsx +114 -0
  70. package/dist/_virtual/_rolldown/runtime.js +0 -11
  71. package/dist/assets/enableAudioAutoplayImage.d.ts +0 -10
  72. package/dist/assets/enableAudioAutoplayImage.js +0 -4
  73. package/dist/collapsible/index.d.ts +0 -31
  74. package/dist/collapsible/index.js +0 -53
  75. package/dist/color.d.ts +0 -116
  76. package/dist/color.js +0 -120
  77. package/dist/feedback.d.ts +0 -20
  78. package/dist/feedback.js +0 -65
  79. package/dist/has-media.d.ts +0 -9
  80. package/dist/has-media.js +0 -10
  81. package/dist/has-text.d.ts +0 -9
  82. package/dist/has-text.js +0 -10
  83. package/dist/html-and-math.d.ts +0 -22
  84. package/dist/html-and-math.js +0 -25
  85. package/dist/index.d.ts +0 -26
  86. package/dist/index.js +0 -18
  87. package/dist/inline-menu.d.ts +0 -33
  88. package/dist/inline-menu.js +0 -29
  89. package/dist/input-container.d.ts +0 -23
  90. package/dist/input-container.js +0 -39
  91. package/dist/preview-layout.d.ts +0 -24
  92. package/dist/preview-layout.js +0 -37
  93. package/dist/preview-prompt.d.ts +0 -38
  94. package/dist/preview-prompt.js +0 -154
  95. package/dist/purpose.d.ts +0 -18
  96. package/dist/purpose.js +0 -11
  97. package/dist/readable.d.ts +0 -18
  98. package/dist/readable.js +0 -11
  99. package/dist/response-indicators.d.ts +0 -121
  100. package/dist/response-indicators.js +0 -62
  101. package/dist/transform-headings.js +0 -15
  102. package/dist/ui-layout.d.ts +0 -31
  103. package/dist/ui-layout.js +0 -55
  104. package/dist/withUndoReset.d.ts +0 -47
  105. package/dist/withUndoReset.js +0 -84
package/src/index.js ADDED
@@ -0,0 +1,35 @@
1
+ import * as indicators from './response-indicators';
2
+ import Feedback from './feedback';
3
+ import Collapsible from './collapsible';
4
+ import withUndoReset from './withUndoReset';
5
+ import PreviewLayout from './preview-layout';
6
+ import UiLayout from './ui-layout';
7
+ import HtmlAndMath from './html-and-math';
8
+ import InputContainer from './input-container';
9
+ import PreviewPrompt from './preview-prompt';
10
+ import Readable from './readable';
11
+ import Purpose from './purpose';
12
+ import * as color from './color';
13
+ import { hasText } from './has-text';
14
+ import { hasMedia } from './has-media';
15
+ import EnableAudioAutoplayImage from './assets/enableAudioAutoplayImage';
16
+ import { transformDataHeadings } from './transform-headings';
17
+
18
+ export {
19
+ HtmlAndMath,
20
+ indicators,
21
+ withUndoReset,
22
+ Feedback,
23
+ UiLayout,
24
+ PreviewLayout,
25
+ Collapsible,
26
+ InputContainer,
27
+ PreviewPrompt,
28
+ color,
29
+ Readable,
30
+ Purpose,
31
+ hasText,
32
+ hasMedia,
33
+ EnableAudioAutoplayImage,
34
+ transformDataHeadings,
35
+ };
@@ -0,0 +1,47 @@
1
+ import InputLabel from '@mui/material/InputLabel';
2
+ import PropTypes from 'prop-types';
3
+ import React from 'react';
4
+ import { styled } from '@mui/material/styles';
5
+ import FormControl from '@mui/material/FormControl';
6
+
7
+ const StyledFormControl = styled(FormControl)(({ theme }) => ({
8
+ margin: 0,
9
+ padding: 0,
10
+ flex: '1 0 auto',
11
+ minWidth: theme.spacing(4),
12
+ }));
13
+
14
+ const StyledInputLabel = styled(InputLabel)(() => ({
15
+ fontSize: 'inherit',
16
+ whiteSpace: 'nowrap',
17
+ margin: 0,
18
+ padding: 0,
19
+ alignSelf: 'flex-start',
20
+ position: 'absolute',
21
+ top: 0,
22
+ left: 0,
23
+ transformOrigin: 'top left',
24
+ pointerEvents: 'none',
25
+ // override MUI's default transform styles
26
+ '&.MuiInputLabel-shrink': {
27
+ transform: 'scale(0.75) translate(0, -0.75em)',
28
+ },
29
+ '&:not(.MuiInputLabel-shrink)': {
30
+ transform: 'translate(0, 0)',
31
+ },
32
+ }));
33
+
34
+ const InputContainer = ({ label, className, children }) => (
35
+ <StyledFormControl className={className}>
36
+ <StyledInputLabel shrink>{label}</StyledInputLabel>
37
+ {children}
38
+ </StyledFormControl>
39
+ );
40
+
41
+ InputContainer.propTypes = {
42
+ label: PropTypes.oneOfType([PropTypes.string, PropTypes.object]).isRequired,
43
+ className: PropTypes.string,
44
+ children: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.node), PropTypes.node]).isRequired,
45
+ };
46
+
47
+ export default InputContainer;
@@ -0,0 +1,41 @@
1
+ import React from 'react';
2
+ import { styled } from '@mui/material/styles';
3
+ import PropTypes from 'prop-types';
4
+ import UiLayout from './ui-layout';
5
+
6
+ const StyledUiLayout = styled(UiLayout)({
7
+ display: 'flex',
8
+ flexDirection: 'column',
9
+ position: 'relative',
10
+ });
11
+
12
+ class PreviewLayout extends React.Component {
13
+ static propTypes = {
14
+ ariaLabel: PropTypes.string,
15
+ children: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.node), PropTypes.node]).isRequired,
16
+ role: PropTypes.string,
17
+ extraCSSRules: PropTypes.shape({
18
+ names: PropTypes.arrayOf(PropTypes.string),
19
+ rules: PropTypes.string,
20
+ }),
21
+ fontSizeFactor: PropTypes.number,
22
+ };
23
+
24
+ render() {
25
+ const { children, ariaLabel, role, extraCSSRules, fontSizeFactor, classes } = this.props;
26
+ const accessibility = ariaLabel ? { 'aria-label': ariaLabel, role } : {};
27
+
28
+ return (
29
+ <StyledUiLayout
30
+ {...accessibility}
31
+ extraCSSRules={extraCSSRules}
32
+ fontSizeFactor={fontSizeFactor}
33
+ classes={classes}
34
+ >
35
+ {children}
36
+ </StyledUiLayout>
37
+ );
38
+ }
39
+ }
40
+
41
+ export default PreviewLayout;
@@ -0,0 +1,280 @@
1
+ import React, { Component } from 'react';
2
+ import { styled } from '@mui/material/styles';
3
+ import PropTypes from 'prop-types';
4
+ import * as color from './color';
5
+ import { renderMath } from '@pie-lib/math-rendering';
6
+
7
+ const StyledPromptContainer = styled('div')(({ theme, tagName }) => ({
8
+ // presentation tables should not have any custom style
9
+ // Base promptTable styles
10
+ '&:not(.MathJax) > table:not([role="presentation"])': {
11
+ borderCollapse: 'collapse',
12
+ },
13
+ // Apply vertical striping when first column is a header (th) and NOT mixed with td
14
+ '&:not(.MathJax) > table:not([role="presentation"]):has(tbody tr > th:first-child):not(:has(tbody tr > td:first-child)) tbody td:nth-child(even)':
15
+ {
16
+ backgroundColor: '#f6f8fa',
17
+ color: theme.palette.common.black,
18
+ },
19
+ // Apply horizontal striping for tables where first element is a data cell (td)
20
+ '&:not(.MathJax) > table:not([role="presentation"]):has(tbody tr > td:first-child) tbody tr:nth-child(even) td': {
21
+ backgroundColor: '#f6f8fa',
22
+ color: theme.palette.common.black,
23
+ },
24
+ // align table content to left as per STAR requirement PD-3687
25
+ '&:not(.MathJax) table:not([role="presentation"]) td, &:not(.MathJax) table:not([role="presentation"]) th': {
26
+ padding: '.6em 1em',
27
+ textAlign: 'left',
28
+ },
29
+ // added this to fix alignment of text in prompt imported from studio (PD-3423)
30
+ '&:not(.MathJax) > table td > p.kds-indent': {
31
+ textAlign: 'initial',
32
+ },
33
+
34
+ // Conditional styles based on class names
35
+ '&.prompt': {
36
+ verticalAlign: 'middle',
37
+ color: color.text(),
38
+ },
39
+ '&.legend': {
40
+ width: '100%',
41
+ fontSize: 'inherit !important',
42
+ },
43
+ '&.rationale': {
44
+ paddingLeft: theme.spacing(4),
45
+ paddingBottom: theme.spacing(1),
46
+ },
47
+ '&.prompt-label': {
48
+ color: `${color.text()} !important`,
49
+ display: 'flex',
50
+ flexDirection: 'column',
51
+ verticalAlign: 'middle',
52
+ cursor: 'pointer',
53
+ '& > p': {
54
+ margin: '0 0 0 0 !important',
55
+ },
56
+ },
57
+ }));
58
+
59
+ //Used these below to replace \\embed{newLine} with \\newline from prompt which will get parsed in MathJax
60
+ const NEWLINE_BLOCK_REGEX = /\\embed\{newLine\}\[\]/g;
61
+ const NEWLINE_LATEX = '\\newline ';
62
+
63
+ export class PreviewPrompt extends Component {
64
+ static propTypes = {
65
+ prompt: PropTypes.string,
66
+ tagName: PropTypes.string,
67
+ className: PropTypes.string,
68
+ onClick: PropTypes.func,
69
+ defaultClassName: PropTypes.string,
70
+ autoplayAudioEnabled: PropTypes.bool,
71
+ customAudioButton: {
72
+ playImage: PropTypes.string,
73
+ pauseImage: PropTypes.string,
74
+ },
75
+ };
76
+
77
+ static defaultProps = {
78
+ onClick: () => {},
79
+ };
80
+
81
+ parsedText = (text) => {
82
+ const { customAudioButton } = this.props;
83
+ const div = document.createElement('div');
84
+ div.innerHTML = text;
85
+
86
+ const audio = div.querySelector('audio');
87
+ if (audio) {
88
+ const source = document.createElement('source');
89
+
90
+ source.setAttribute('type', 'audio/mp3');
91
+ source.setAttribute('src', audio.getAttribute('src'));
92
+
93
+ audio.removeAttribute('src');
94
+ audio.setAttribute('id', 'pie-prompt-audio-player');
95
+
96
+ audio.appendChild(source);
97
+
98
+ if (customAudioButton) {
99
+ audio.style.display = 'none';
100
+
101
+ const playButton = document.createElement('div');
102
+ playButton.id = 'play-audio-button';
103
+
104
+ Object.assign(playButton.style, {
105
+ cursor: 'pointer',
106
+ display: 'block',
107
+ width: '128px',
108
+ height: '128px',
109
+ backgroundImage: `url(${customAudioButton.pauseImage})`,
110
+ backgroundSize: 'cover',
111
+ borderRadius: '50%',
112
+ border: '1px solid #326295',
113
+ });
114
+
115
+ audio.parentNode.insertBefore(playButton, audio);
116
+ }
117
+ }
118
+
119
+ return div.innerHTML;
120
+ };
121
+
122
+ addCustomAudioButtonControls() {
123
+ const { autoplayAudioEnabled, customAudioButton } = this.props;
124
+ const playButton = document.getElementById('play-audio-button');
125
+ const audio = document.getElementById('pie-prompt-audio-player');
126
+
127
+ if (autoplayAudioEnabled && audio) {
128
+ audio
129
+ .play()
130
+ .then(() => {
131
+ if (playButton && customAudioButton) {
132
+ audio.addEventListener('ended', handleAudioEnded);
133
+ }
134
+ })
135
+ .catch((error) => {
136
+ console.error('Error playing audio', error);
137
+ });
138
+ }
139
+
140
+ if (!playButton || !audio || !customAudioButton) return;
141
+
142
+ const handlePlayClick = () => {
143
+ // if already playing, don't play again
144
+ if (!audio.paused) return;
145
+ if (playButton.style.backgroundImage.includes(customAudioButton.pauseImage)) return;
146
+
147
+ audio.play();
148
+ };
149
+
150
+ const handleAudioEnded = () => {
151
+ playButton.style.backgroundImage = `url(${customAudioButton.playImage})`;
152
+ };
153
+
154
+ const handleAudioPlay = () => {
155
+ Object.assign(playButton.style, {
156
+ backgroundImage: `url(${customAudioButton.pauseImage})`,
157
+ border: '1px solid #ccc',
158
+ });
159
+ };
160
+
161
+ const handleAudioPause = () => {
162
+ Object.assign(playButton.style, {
163
+ backgroundImage: `url(${customAudioButton.playImage})`,
164
+ border: '1px solid #326295',
165
+ });
166
+ };
167
+
168
+ playButton.addEventListener('click', handlePlayClick);
169
+ audio.addEventListener('play', handleAudioPlay);
170
+ audio.addEventListener('pause', handleAudioPause);
171
+ audio.addEventListener('ended', handleAudioEnded);
172
+
173
+ // store event handler references so they can be removed later
174
+ this._handlePlayClick = handlePlayClick;
175
+ this._handleAudioPlay = handleAudioPlay;
176
+ this._handleAudioPause = handleAudioPause;
177
+ this._handleAudioEnded = handleAudioEnded;
178
+ }
179
+
180
+ removeCustomAudioButtonListeners() {
181
+ const playButton = document.getElementById('play-audio-button');
182
+ const audio = document.querySelector('audio');
183
+
184
+ if (!playButton || !audio) return;
185
+
186
+ // remove event listeners using stored references
187
+ playButton.removeEventListener('click', this._handlePlayClick);
188
+ audio.removeEventListener('play', this._handleAudioPlay);
189
+ audio.removeEventListener('pause', this._handleAudioPause);
190
+ audio.removeEventListener('ended', this._handleAudioEnded);
191
+ }
192
+
193
+ componentDidMount() {
194
+ this.alignImages();
195
+ this.addCustomAudioButtonControls();
196
+ this.setupMathRendering();
197
+ }
198
+
199
+ componentDidUpdate(prevProps) {
200
+ this.alignImages();
201
+
202
+ if (prevProps.prompt !== this.props.prompt) {
203
+ this.renderMathContent();
204
+ }
205
+ }
206
+
207
+ componentWillUnmount() {
208
+ this.removeCustomAudioButtonListeners();
209
+ }
210
+
211
+ setupMathRendering() {
212
+ this.renderMathContent();
213
+ }
214
+
215
+ renderMathContent() {
216
+ const container = document.getElementById('preview-prompt');
217
+ if (container && typeof renderMath === 'function') {
218
+ renderMath(container);
219
+ }
220
+ }
221
+
222
+ alignImages() {
223
+ const previewPrompts = document.querySelectorAll('#preview-prompt');
224
+
225
+ previewPrompts.forEach((previewPrompt) => {
226
+ const images = previewPrompt.getElementsByTagName('img');
227
+
228
+ if (images && images.length) {
229
+ for (let image of images) {
230
+ if (image.attributes && image.attributes.alignment && image.attributes.alignment.value) {
231
+ const alignment = image.attributes.alignment.value;
232
+ const justifyContent =
233
+ alignment === 'center' ? 'center' : alignment === 'right' ? 'flex-end' : 'flex-start';
234
+
235
+ const parentNode = image.parentElement;
236
+
237
+ if (
238
+ parentNode.tagName === 'DIV' &&
239
+ parentNode.style.display === 'flex' &&
240
+ parentNode.style.width === '100%'
241
+ ) {
242
+ parentNode.style.justifyContent = justifyContent;
243
+ } else {
244
+ const div = document.createElement('div');
245
+ div.style.display = 'flex';
246
+ div.style.width = '100%';
247
+ div.style.justifyContent = justifyContent;
248
+
249
+ const copyImage = image.cloneNode(true);
250
+ div.appendChild(copyImage);
251
+ parentNode.replaceChild(div, image);
252
+ }
253
+ }
254
+ }
255
+ }
256
+ });
257
+ }
258
+
259
+ render() {
260
+ const { prompt, tagName, className, onClick, defaultClassName } = this.props;
261
+ // legend tag was added once with accessibility tasks, we need extra style to make it work with images alignment
262
+ const legendClass = tagName === 'legend' ? 'legend' : '';
263
+ const customClasses = `${className || ''} ${defaultClassName || ''} ${legendClass}`.trim();
264
+
265
+ return (
266
+ <StyledPromptContainer
267
+ as={tagName || 'div'}
268
+ id={'preview-prompt'}
269
+ onClick={onClick}
270
+ className={customClasses}
271
+ tagName={tagName}
272
+ dangerouslySetInnerHTML={{
273
+ __html: this.parsedText(prompt || '').replace(NEWLINE_BLOCK_REGEX, NEWLINE_LATEX),
274
+ }}
275
+ />
276
+ );
277
+ }
278
+ }
279
+
280
+ export default PreviewPrompt;
@@ -0,0 +1,17 @@
1
+ import PropTypes from 'prop-types';
2
+ import React from 'react';
3
+
4
+ const Purpose = (props) => {
5
+ return (
6
+ <>
7
+ {React.Children.map(props.children, (child) => React.cloneElement(child, { 'data-pie-purpose': props.purpose }))}
8
+ </>
9
+ );
10
+ };
11
+
12
+ Purpose.propTypes = {
13
+ children: PropTypes.node,
14
+ purpose: PropTypes.string,
15
+ };
16
+
17
+ export default Purpose;
@@ -0,0 +1,19 @@
1
+ import PropTypes from 'prop-types';
2
+ import React from 'react';
3
+
4
+ const Readable = (props) => {
5
+ return (
6
+ <>
7
+ {React.Children.map(props.children, (child) =>
8
+ React.cloneElement(child, { 'data-pie-readable': props.false === undefined }),
9
+ )}
10
+ </>
11
+ );
12
+ };
13
+
14
+ Readable.propTypes = {
15
+ children: PropTypes.node,
16
+ false: PropTypes.bool,
17
+ };
18
+
19
+ export default Readable;
@@ -0,0 +1,82 @@
1
+ import React from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import * as icons from '@pie-lib/icons';
4
+ import Popover from '@mui/material/Popover';
5
+ import { styled } from '@mui/material/styles';
6
+ import Feedback from './feedback';
7
+ import debug from 'debug';
8
+
9
+ const log = debug('pie-libs:render-ui:response-indicators');
10
+
11
+ const ResponseIndicatorContainer = styled('div')(({ hasFeedback }) => ({
12
+ cursor: hasFeedback ? 'pointer' : 'default',
13
+ }));
14
+
15
+ const StyledPopover = styled(Popover)({
16
+ cursor: 'pointer',
17
+ });
18
+
19
+ const PopoverPaper = styled('div')({
20
+ padding: '0',
21
+ borderRadius: '4px',
22
+ });
23
+
24
+ const BuildIndicator = (Icon, correctness) => {
25
+ class Indicator extends React.Component {
26
+ constructor(props) {
27
+ super(props);
28
+ this.state = {};
29
+ }
30
+
31
+ handlePopoverOpen = (event) => {
32
+ log('[handlePopoverOpen]', event.target);
33
+ this.setState({ anchorEl: event.target });
34
+ };
35
+
36
+ handlePopoverClose = () => {
37
+ this.setState({ anchorEl: null });
38
+ };
39
+
40
+ render() {
41
+ const { feedback } = this.props;
42
+ const { anchorEl } = this.state;
43
+ return (
44
+ <ResponseIndicatorContainer hasFeedback={!!feedback}>
45
+ <span ref={(r) => (this.icon = r)} onClick={this.handlePopoverOpen}>
46
+ <Icon />
47
+ </span>
48
+
49
+ {feedback && (
50
+ <StyledPopover
51
+ PaperComponent={PopoverPaper}
52
+ open={!!anchorEl}
53
+ anchorEl={anchorEl}
54
+ anchorOrigin={{
55
+ vertical: 'bottom',
56
+ horizontal: 'left',
57
+ }}
58
+ transformOrigin={{
59
+ vertical: 'top',
60
+ horizontal: 'left',
61
+ }}
62
+ onClose={this.handlePopoverClose}
63
+ >
64
+ <Feedback feedback={feedback} correctness={correctness} />
65
+ </StyledPopover>
66
+ )}
67
+ </ResponseIndicatorContainer>
68
+ );
69
+ }
70
+ }
71
+
72
+ Indicator.propTypes = {
73
+ feedback: PropTypes.string,
74
+ };
75
+
76
+ return Indicator;
77
+ };
78
+
79
+ export const Correct = BuildIndicator(icons.Correct, 'correct');
80
+ export const Incorrect = BuildIndicator(icons.Incorrect, 'incorrect');
81
+ export const PartiallyCorrect = BuildIndicator(icons.PartiallyCorrect, 'partially-correct');
82
+ export const NothingSubmitted = BuildIndicator(icons.NothingSubmitted, 'nothing-submitted');
@@ -1,11 +1,3 @@
1
- /**
2
- * @synced-from pie-lib/packages/render-ui/src/transform-headings.js
3
- * @auto-generated
4
- *
5
- * This file is automatically synced from pie-elements and converted to TypeScript.
6
- * Manual edits will be overwritten on next sync.
7
- * To make changes, edit the upstream JavaScript file and run sync again.
8
- */
9
1
  /**
10
2
  * transformDataHeadings
11
3
  *
@@ -26,4 +18,33 @@
26
18
  * // baseLevel=2, data-heading="heading2" → <h3 data-heading="heading2">…</h3>
27
19
  * // baseLevel=3, data-heading="heading1" → <h3 data-heading="heading1">…</h3>
28
20
  */
29
- export declare function transformDataHeadings(html?: string, baseLevel?: number): string;
21
+ export function transformDataHeadings(html = '', baseLevel = 2) {
22
+ if (!html) return html;
23
+
24
+ const parser = new DOMParser();
25
+ const doc = parser.parseFromString(html, 'text/html');
26
+
27
+ doc.body.querySelectorAll('[data-heading]').forEach((el) => {
28
+ const raw = el.getAttribute('data-heading') || '';
29
+ const n = parseInt(raw.replace('heading', ''), 10);
30
+
31
+ if (!Number.isFinite(n)) return; // skip malformed values
32
+
33
+ const level = Math.min(6, Math.max(1, baseLevel + n - 1));
34
+ const heading = doc.createElement(`h${level}`);
35
+
36
+ heading.setAttribute('data-heading', raw);
37
+
38
+ // Copy all other attributes (class, id, style, …)
39
+ Array.from(el.attributes).forEach((attr) => {
40
+ if (attr.name !== 'data-heading') {
41
+ heading.setAttribute(attr.name, attr.value);
42
+ }
43
+ });
44
+
45
+ heading.innerHTML = el.innerHTML;
46
+ el.replaceWith(heading);
47
+ });
48
+
49
+ return doc.body.innerHTML;
50
+ }
@@ -0,0 +1,96 @@
1
+ import React from 'react';
2
+ import { createTheme, styled, StyledEngineProvider, ThemeProvider } from '@mui/material/styles';
3
+ import PropTypes from 'prop-types';
4
+
5
+ const theme = createTheme({
6
+ typography: {
7
+ fontFamily: 'inherit',
8
+ },
9
+ palette: {
10
+ action: {
11
+ disabled: 'rgba(0, 0, 0, 0.54);',
12
+ },
13
+ },
14
+ components: {
15
+ MuiTypography: {
16
+ styleOverrides: {
17
+ root: { fontFamily: 'inherit' },
18
+ },
19
+ },
20
+ MuiButton: {
21
+ styleOverrides: {
22
+ contained: {
23
+ backgroundColor: '#e0e0e0',
24
+ color: '#000000',
25
+ '&:hover': {
26
+ backgroundColor: '#bdbdbd',
27
+ },
28
+ },
29
+ },
30
+ },
31
+ },
32
+ });
33
+
34
+ const StyledContainer = styled('div')({
35
+ // need this because some browsers set their own style on table
36
+ '& table, th, td': {
37
+ fontSize: 'inherit' /* Ensure table elements inherit font size */,
38
+ },
39
+ });
40
+
41
+ class UiLayout extends React.Component {
42
+ static propTypes = {
43
+ className: PropTypes.string,
44
+ children: PropTypes.array,
45
+ extraCSSRules: PropTypes.shape({
46
+ names: PropTypes.arrayOf(PropTypes.string),
47
+ rules: PropTypes.string,
48
+ }),
49
+ fontSizeFactor: PropTypes.number,
50
+ };
51
+
52
+ static defaultProps = {
53
+ extraCSSRules: {},
54
+ fontSizeFactor: 1,
55
+ };
56
+
57
+ constructor(props) {
58
+ super(props);
59
+ this.classesSheet = document.createElement('style');
60
+ }
61
+
62
+ computeStyle(fontSizeFactor) {
63
+ const getFontSize = (element) => parseFloat(getComputedStyle(element).fontSize);
64
+
65
+ const rootFontSize = getFontSize(document.documentElement);
66
+ const bodyFontSize = getFontSize(document.body);
67
+ const effectiveFontSize = Math.max(rootFontSize, bodyFontSize);
68
+
69
+ // Handle null, undefined, or invalid values by defaulting to 1
70
+ const factor = fontSizeFactor != null && typeof fontSizeFactor === 'number' ? fontSizeFactor : 1;
71
+ return factor !== 1 ? { fontSize: `${effectiveFontSize * factor}px` } : null;
72
+ }
73
+
74
+ render() {
75
+ const { children, className, fontSizeFactor, ...rest } = this.props;
76
+
77
+ const { extraCSSRules, ...restProps } = rest;
78
+ const style = this.computeStyle(fontSizeFactor);
79
+
80
+ return (
81
+ <StyledEngineProvider injectFirst>
82
+ <ThemeProvider theme={theme}>
83
+ {extraCSSRules?.rules ? (
84
+ <style dangerouslySetInnerHTML={{ __html: `.extraCSSRules { ${extraCSSRules.rules} }` }} />
85
+ ) : null}
86
+
87
+ <StyledContainer className={`${className} extraCSSRules`} {...restProps} {...(style && { style })}>
88
+ {children}
89
+ </StyledContainer>
90
+ </ThemeProvider>
91
+ </StyledEngineProvider>
92
+ );
93
+ }
94
+ }
95
+
96
+ export default UiLayout;