@pie-lib/editable-html-tip-tap 2.1.12 → 2.1.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/lib/components/MenuBar.js +70 -10
- package/lib/components/MenuBar.js.map +1 -1
- package/lib/components/common/toolbar-buttons.js +6 -5
- package/lib/components/common/toolbar-buttons.js.map +1 -1
- package/lib/components/icons/TableIcons.js +3 -1
- package/lib/components/icons/TableIcons.js.map +1 -1
- package/lib/components/icons/TextAlign.js +14 -5
- package/lib/components/icons/TextAlign.js.map +1 -1
- package/lib/components/respArea/MathTemplated.js +12 -6
- package/lib/components/respArea/MathTemplated.js.map +1 -1
- package/package.json +5 -5
- package/src/components/MenuBar.jsx +80 -7
- package/src/components/__tests__/MenuBar.test.jsx +97 -9
- package/src/components/__tests__/toolbar-buttons.test.jsx +15 -2
- package/src/components/common/toolbar-buttons.jsx +6 -4
- package/src/components/icons/TableIcons.jsx +9 -1
- package/src/components/icons/TextAlign.jsx +39 -5
- package/src/components/respArea/MathTemplated.jsx +12 -5
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MathTemplated.js","names":["_react","_interopRequireDefault","require","_propTypes","_react2","_mathInput","_styles","StyledSpanContainer","styled","display","border","margin","cursor","alignItems","justifyContent","minWidth","minHeight","height","StyledResponseBox","
|
|
1
|
+
{"version":3,"file":"MathTemplated.js","names":["_react","_interopRequireDefault","require","_propTypes","_react2","_mathInput","_styles","_renderUi","StyledSpanContainer","styled","display","border","concat","color","margin","cursor","alignItems","justifyContent","minWidth","minHeight","height","StyledResponseBox","background","backgroundDark","text","borderRight","boxSizing","overflow","fontSize","fontFamily","padding","StyledMathBlock","flex","backgroundColor","verticalAlign","borderTop","marginLeft","marginTop","paddingTop","lineHeight","top","left","position","right","MathTemplated","props","node","options","selected","attributes","attrs","value","index","keyToDisplay","parseInt","createElement","NodeViewWrapper","className","style","mq","Static","latex","propTypes","PropTypes","object","string","_default","exports"],"sources":["../../../src/components/respArea/MathTemplated.jsx"],"sourcesContent":["import React from 'react';\nimport PropTypes from 'prop-types';\nimport { NodeViewWrapper } from '@tiptap/react';\nimport { mq } from '@pie-lib/math-input';\nimport { styled } from '@mui/material/styles';\nimport { color } from '@pie-lib/render-ui';\n\nconst StyledSpanContainer = styled('span')(() => ({\n display: 'inline-flex',\n /*\n * #C0C3CF is `--pie-blue-grey-300`'s default value, but that token is a fill: it\n * measures 1.00:1 against black-on-rose's own background. The box outline is what\n * marks the response slot in running text, so it takes the stroke token instead.\n */\n border: `1px solid ${color.border()}`,\n margin: '1px 5px',\n cursor: 'pointer',\n alignItems: 'center',\n justifyContent: 'center',\n minWidth: '50px',\n minHeight: '36px',\n height: 'fit-content',\n}));\n\nconst StyledResponseBox = styled('div')(() => ({\n // Fill and ink move together so the label stays legible on whatever the fill becomes.\n background: color.backgroundDark(),\n color: color.text(),\n display: 'inline-flex',\n borderRight: `2px solid ${color.border()}`,\n boxSizing: 'border-box',\n overflow: 'hidden',\n fontSize: '12px',\n minHeight: '36px',\n height: '100%',\n alignItems: 'center',\n fontFamily: 'Symbola, Times New Roman, serif',\n padding: '0 2px',\n}));\n\nconst StyledMathBlock = styled('div')(() => ({\n flex: 8,\n color: 'var(--pie-text, black)',\n padding: '4px !important',\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n backgroundColor: 'var(--pie-background, rgba(255, 255, 255, 0))',\n '& > .mq-math-mode sup.mq-nthroot': {\n fontSize: '70% !important',\n verticalAlign: '1em !important',\n },\n '& > .mq-math-mode .mq-sqrt-stem': {\n borderTop: '0.07em solid',\n marginLeft: '-1.5px',\n marginTop: '-2px !important',\n paddingTop: '5px !important',\n },\n '& .mq-overarrow-inner': {\n paddingTop: '0 !important',\n border: 'none !important',\n },\n '& .mq-overarrow.mq-arrow-both': {\n marginTop: '0px',\n minWidth: '1.23em',\n '& *': {\n lineHeight: '1 !important',\n },\n '&:before': {\n top: '-0.4em',\n left: '-1px',\n },\n '&:after': {\n top: '0px !important',\n position: 'absolute !important',\n right: '-2px',\n },\n '&.mq-empty:after': {\n top: '-0.45em',\n },\n },\n '& .mq-overarrow.mq-arrow-right': {\n '&:before': {\n top: '-0.4em',\n right: '-1px',\n },\n },\n '& .mq-overarrow-inner-right': {\n display: 'none !important',\n },\n '& .mq-overarrow-inner-left': {\n display: 'none !important',\n },\n}));\nconst MathTemplated = (props) => {\n const { node, options, selected } = props;\n const { attrs: attributes } = node;\n const { value, index } = attributes;\n\n // add 1 to index to display R 1 instead of R 0\n const keyToDisplay = `R ${parseInt(index) + 1}`;\n\n // console.log({nodeProps.children})\n return (\n <NodeViewWrapper\n className=\"math-templated\"\n data-selected={selected}\n style={{\n display: 'inline-flex',\n minHeight: '36px',\n minWidth: '50px',\n cursor: 'pointer',\n }}\n >\n <StyledSpanContainer {...attributes}>\n <StyledResponseBox>{keyToDisplay}</StyledResponseBox>\n <StyledMathBlock>\n <mq.Static latex={value} />\n </StyledMathBlock>\n </StyledSpanContainer>\n </NodeViewWrapper>\n );\n};\n\nMathTemplated.propTypes = {\n attributes: PropTypes.object,\n value: PropTypes.string,\n keyToDisplay: PropTypes.string,\n};\n\nexport default MathTemplated;\n"],"mappings":";;;;;;;AAAA,IAAAA,MAAA,GAAAC,sBAAA,CAAAC,OAAA;AACA,IAAAC,UAAA,GAAAF,sBAAA,CAAAC,OAAA;AACA,IAAAE,OAAA,GAAAF,OAAA;AACA,IAAAG,UAAA,GAAAH,OAAA;AACA,IAAAI,OAAA,GAAAJ,OAAA;AACA,IAAAK,SAAA,GAAAL,OAAA;AAEA,IAAMM,mBAAmB,GAAG,IAAAC,cAAM,EAAC,MAAM,CAAC,CAAC;EAAA,OAAO;IAChDC,OAAO,EAAE,aAAa;IACtB;AACF;AACA;AACA;AACA;IACEC,MAAM,eAAAC,MAAA,CAAeC,eAAK,CAACF,MAAM,CAAC,CAAC,CAAE;IACrCG,MAAM,EAAE,SAAS;IACjBC,MAAM,EAAE,SAAS;IACjBC,UAAU,EAAE,QAAQ;IACpBC,cAAc,EAAE,QAAQ;IACxBC,QAAQ,EAAE,MAAM;IAChBC,SAAS,EAAE,MAAM;IACjBC,MAAM,EAAE;EACV,CAAC;AAAA,CAAC,CAAC;AAEH,IAAMC,iBAAiB,GAAG,IAAAZ,cAAM,EAAC,KAAK,CAAC,CAAC;EAAA,OAAO;IAC7C;IACAa,UAAU,EAAET,eAAK,CAACU,cAAc,CAAC,CAAC;IAClCV,KAAK,EAAEA,eAAK,CAACW,IAAI,CAAC,CAAC;IACnBd,OAAO,EAAE,aAAa;IACtBe,WAAW,eAAAb,MAAA,CAAeC,eAAK,CAACF,MAAM,CAAC,CAAC,CAAE;IAC1Ce,SAAS,EAAE,YAAY;IACvBC,QAAQ,EAAE,QAAQ;IAClBC,QAAQ,EAAE,MAAM;IAChBT,SAAS,EAAE,MAAM;IACjBC,MAAM,EAAE,MAAM;IACdJ,UAAU,EAAE,QAAQ;IACpBa,UAAU,EAAE,iCAAiC;IAC7CC,OAAO,EAAE;EACX,CAAC;AAAA,CAAC,CAAC;AAEH,IAAMC,eAAe,GAAG,IAAAtB,cAAM,EAAC,KAAK,CAAC,CAAC;EAAA,OAAO;IAC3CuB,IAAI,EAAE,CAAC;IACPnB,KAAK,EAAE,wBAAwB;IAC/BiB,OAAO,EAAE,gBAAgB;IACzBpB,OAAO,EAAE,MAAM;IACfM,UAAU,EAAE,QAAQ;IACpBC,cAAc,EAAE,QAAQ;IACxBgB,eAAe,EAAE,+CAA+C;IAChE,kCAAkC,EAAE;MAClCL,QAAQ,EAAE,gBAAgB;MAC1BM,aAAa,EAAE;IACjB,CAAC;IACD,iCAAiC,EAAE;MACjCC,SAAS,EAAE,cAAc;MACzBC,UAAU,EAAE,QAAQ;MACpBC,SAAS,EAAE,iBAAiB;MAC5BC,UAAU,EAAE;IACd,CAAC;IACD,uBAAuB,EAAE;MACvBA,UAAU,EAAE,cAAc;MAC1B3B,MAAM,EAAE;IACV,CAAC;IACD,+BAA+B,EAAE;MAC/B0B,SAAS,EAAE,KAAK;MAChBnB,QAAQ,EAAE,QAAQ;MAClB,KAAK,EAAE;QACLqB,UAAU,EAAE;MACd,CAAC;MACD,UAAU,EAAE;QACVC,GAAG,EAAE,QAAQ;QACbC,IAAI,EAAE;MACR,CAAC;MACD,SAAS,EAAE;QACTD,GAAG,EAAE,gBAAgB;QACrBE,QAAQ,EAAE,qBAAqB;QAC/BC,KAAK,EAAE;MACT,CAAC;MACD,kBAAkB,EAAE;QAClBH,GAAG,EAAE;MACP;IACF,CAAC;IACD,gCAAgC,EAAE;MAChC,UAAU,EAAE;QACVA,GAAG,EAAE,QAAQ;QACbG,KAAK,EAAE;MACT;IACF,CAAC;IACD,6BAA6B,EAAE;MAC7BjC,OAAO,EAAE;IACX,CAAC;IACD,4BAA4B,EAAE;MAC5BA,OAAO,EAAE;IACX;EACF,CAAC;AAAA,CAAC,CAAC;AACH,IAAMkC,aAAa,GAAG,SAAhBA,aAAaA,CAAIC,KAAK,EAAK;EAC/B,IAAQC,IAAI,GAAwBD,KAAK,CAAjCC,IAAI;IAAEC,OAAO,GAAeF,KAAK,CAA3BE,OAAO;IAAEC,QAAQ,GAAKH,KAAK,CAAlBG,QAAQ;EAC/B,IAAeC,UAAU,GAAKH,IAAI,CAA1BI,KAAK;EACb,IAAQC,KAAK,GAAYF,UAAU,CAA3BE,KAAK;IAAEC,KAAK,GAAKH,UAAU,CAApBG,KAAK;;EAEpB;EACA,IAAMC,YAAY,QAAAzC,MAAA,CAAQ0C,QAAQ,CAACF,KAAK,CAAC,GAAG,CAAC,CAAE;;EAE/C;EACA,oBACEpD,MAAA,YAAAuD,aAAA,CAACnD,OAAA,CAAAoD,eAAe;IACdC,SAAS,EAAC,gBAAgB;IAC1B,iBAAeT,QAAS;IACxBU,KAAK,EAAE;MACLhD,OAAO,EAAE,aAAa;MACtBS,SAAS,EAAE,MAAM;MACjBD,QAAQ,EAAE,MAAM;MAChBH,MAAM,EAAE;IACV;EAAE,gBAEFf,MAAA,YAAAuD,aAAA,CAAC/C,mBAAmB,EAAKyC,UAAU,eACjCjD,MAAA,YAAAuD,aAAA,CAAClC,iBAAiB,QAAEgC,YAAgC,CAAC,eACrDrD,MAAA,YAAAuD,aAAA,CAACxB,eAAe,qBACd/B,MAAA,YAAAuD,aAAA,CAAClD,UAAA,CAAAsD,EAAE,CAACC,MAAM;IAACC,KAAK,EAAEV;EAAM,CAAE,CACX,CACE,CACN,CAAC;AAEtB,CAAC;AAEDP,aAAa,CAACkB,SAAS,GAAG;EACxBb,UAAU,EAAEc,qBAAS,CAACC,MAAM;EAC5Bb,KAAK,EAAEY,qBAAS,CAACE,MAAM;EACvBZ,YAAY,EAAEU,qBAAS,CAACE;AAC1B,CAAC;AAAC,IAAAC,QAAA,GAAAC,OAAA,cAEavB,aAAa","ignoreList":[]}
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"publishConfig": {
|
|
4
4
|
"access": "public"
|
|
5
5
|
},
|
|
6
|
-
"version": "2.1.
|
|
6
|
+
"version": "2.1.14",
|
|
7
7
|
"description": "",
|
|
8
8
|
"license": "ISC",
|
|
9
9
|
"main": "lib/index.js",
|
|
@@ -16,11 +16,11 @@
|
|
|
16
16
|
"@dnd-kit/utilities": "3.2.2",
|
|
17
17
|
"@mui/icons-material": "^7.3.4",
|
|
18
18
|
"@mui/material": "^7.3.4",
|
|
19
|
-
"@pie-lib/drag": "^4.1.
|
|
19
|
+
"@pie-lib/drag": "^4.1.1",
|
|
20
20
|
"@pie-lib/math-input": "^8.1.1",
|
|
21
21
|
"@pie-lib/math-rendering": "^5.1.0",
|
|
22
|
-
"@pie-lib/math-toolbar": "^3.0.
|
|
23
|
-
"@pie-lib/render-ui": "^
|
|
22
|
+
"@pie-lib/math-toolbar": "^3.0.7",
|
|
23
|
+
"@pie-lib/render-ui": "^7.0.0",
|
|
24
24
|
"@tiptap/core": "3.20.0",
|
|
25
25
|
"@tiptap/extension-character-count": "3.20.0",
|
|
26
26
|
"@tiptap/extension-color": "3.20.0",
|
|
@@ -59,6 +59,6 @@
|
|
|
59
59
|
"peerDependencies": {
|
|
60
60
|
"react": "^18.2.0"
|
|
61
61
|
},
|
|
62
|
-
"gitHead": "
|
|
62
|
+
"gitHead": "e6b46157ab321f4488117149f21783511f30970c",
|
|
63
63
|
"scripts": {}
|
|
64
64
|
}
|
|
@@ -2,6 +2,7 @@ import React, { useMemo, useState } from 'react';
|
|
|
2
2
|
import classNames from 'classnames';
|
|
3
3
|
import { styled, useTheme } from '@mui/material/styles';
|
|
4
4
|
import { NodeSelection } from 'prosemirror-state';
|
|
5
|
+
import { color } from '@pie-lib/render-ui';
|
|
5
6
|
|
|
6
7
|
import Bold from '@mui/icons-material/FormatBold';
|
|
7
8
|
import Italic from '@mui/icons-material/FormatItalic';
|
|
@@ -34,7 +35,15 @@ import { CharacterIcon, CharacterPicker } from './CharacterPicker';
|
|
|
34
35
|
import { DoneButton } from './common/done-button';
|
|
35
36
|
|
|
36
37
|
const SuperscriptIcon = () => (
|
|
37
|
-
<svg
|
|
38
|
+
<svg
|
|
39
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
40
|
+
height="24px"
|
|
41
|
+
viewBox="0 0 24 24"
|
|
42
|
+
width="24px"
|
|
43
|
+
fill="none"
|
|
44
|
+
aria-hidden="true"
|
|
45
|
+
focusable="false"
|
|
46
|
+
>
|
|
38
47
|
<path
|
|
39
48
|
d="M22,7h-2v1h3v1h-4V7c0-0.55,0.45-1,1-1h2V5h-3V4h3c0.55,0,1,0.45,1,1v1C23,6.55,22.55,7,22,7z M5.88,20h2.66l3.4-5.42h0.12 l3.4,5.42h2.66l-4.65-7.27L17.81,6h-2.68l-3.07,4.99h-0.12L8.85,6H6.19l4.32,6.73L5.88,20z"
|
|
40
49
|
fill="currentColor"
|
|
@@ -43,7 +52,15 @@ const SuperscriptIcon = () => (
|
|
|
43
52
|
);
|
|
44
53
|
|
|
45
54
|
const SubscriptIcon = () => (
|
|
46
|
-
<svg
|
|
55
|
+
<svg
|
|
56
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
57
|
+
height="24px"
|
|
58
|
+
viewBox="0 0 24 24"
|
|
59
|
+
width="24px"
|
|
60
|
+
fill="none"
|
|
61
|
+
aria-hidden="true"
|
|
62
|
+
focusable="false"
|
|
63
|
+
>
|
|
47
64
|
<path
|
|
48
65
|
d="M22,18h-2v1h3v1h-4v-2c0-0.55,0.45-1,1-1h2v-1h-3v-1h3c0.55,0,1,0.45,1,1v1C23,17.55,22.55,18,22,18z M5.88,18h2.66 l3.4-5.42h0.12l3.4,5.42h2.66l-4.65-7.27L17.81,4h-2.68l-3.07,4.99h-0.12L8.85,4H6.19l4.32,6.73L5.88,18z"
|
|
49
66
|
fill="currentColor"
|
|
@@ -59,6 +76,8 @@ const HeadingIcon = () => (
|
|
|
59
76
|
fill="none"
|
|
60
77
|
xmlns="http://www.w3.org/2000/svg"
|
|
61
78
|
style={{ width: '20px', height: '18px' }}
|
|
79
|
+
aria-hidden="true"
|
|
80
|
+
focusable="false"
|
|
62
81
|
>
|
|
63
82
|
<path
|
|
64
83
|
d="M27 4V24H29C29.5 24 30 24.5 30 25V27C30 27.5625 29.5 28 29 28H19C18.4375 28 18 27.5625 18 27V25C18 24.5 18.4375 24 19 24H21V16H9V24H11C11.5 24 12 24.5 12 25V27C12 27.5625 11.5 28 11 28H1C0.4375 28 0 27.5625 0 27V25C0 24.5 0.4375 24 1 24H3V4H1C0.4375 4 0 3.5625 0 3V1C0 0.5 0.4375 0 1 0H11C11.5 0 12 0.5 12 1V3C12 3.5625 11.5 4 11 4H9V12H21V4H19C18.4375 4 18 3.5625 18 3V1C18 0.5 18.4375 0 19 0H29C29.5 0 30 0.5 30 1V3C30 3.5625 29.5 4 29 4H27Z"
|
|
@@ -166,6 +185,7 @@ function MenuBar({
|
|
|
166
185
|
() => [
|
|
167
186
|
{
|
|
168
187
|
icon: <GridOn />,
|
|
188
|
+
label: 'Insert table',
|
|
169
189
|
onClick: (editor) => editor.chain().focus().insertTable({ rows: 2, cols: 2, withHeaderRow: false }).run(),
|
|
170
190
|
hidden: (state) => !activePlugins?.includes('table') || state.isTable,
|
|
171
191
|
isActive: (state) => state.isTable,
|
|
@@ -173,6 +193,7 @@ function MenuBar({
|
|
|
173
193
|
},
|
|
174
194
|
{
|
|
175
195
|
icon: <AddRow />,
|
|
196
|
+
label: 'Add table row',
|
|
176
197
|
onClick: (editor) => editor.chain().focus().addRowAfter().run(),
|
|
177
198
|
hidden: (state) => !(state.isTable && !state.hasTextSelectionInTable),
|
|
178
199
|
isActive: (state) => state.isTable,
|
|
@@ -180,6 +201,7 @@ function MenuBar({
|
|
|
180
201
|
},
|
|
181
202
|
{
|
|
182
203
|
icon: <RemoveRow />,
|
|
204
|
+
label: 'Remove table row',
|
|
183
205
|
onClick: (editor) => editor.chain().focus().deleteRow().run(),
|
|
184
206
|
hidden: (state) => !(state.isTable && !state.hasTextSelectionInTable),
|
|
185
207
|
isActive: (state) => state.isTable,
|
|
@@ -187,6 +209,7 @@ function MenuBar({
|
|
|
187
209
|
},
|
|
188
210
|
{
|
|
189
211
|
icon: <AddColumn />,
|
|
212
|
+
label: 'Add table column',
|
|
190
213
|
onClick: (editor) => editor.chain().focus().addColumnAfter().run(),
|
|
191
214
|
hidden: (state) => !(state.isTable && !state.hasTextSelectionInTable),
|
|
192
215
|
isActive: (state) => state.isTable,
|
|
@@ -194,6 +217,7 @@ function MenuBar({
|
|
|
194
217
|
},
|
|
195
218
|
{
|
|
196
219
|
icon: <RemoveColumn />,
|
|
220
|
+
label: 'Remove table column',
|
|
197
221
|
onClick: (editor) => editor.chain().focus().deleteColumn().run(),
|
|
198
222
|
hidden: (state) => !(state.isTable && !state.hasTextSelectionInTable),
|
|
199
223
|
isActive: (state) => state.isTable,
|
|
@@ -201,6 +225,7 @@ function MenuBar({
|
|
|
201
225
|
},
|
|
202
226
|
{
|
|
203
227
|
icon: <RemoveTable />,
|
|
228
|
+
label: 'Remove table',
|
|
204
229
|
onClick: (editor) => editor.chain().focus().deleteTable().run(),
|
|
205
230
|
hidden: (state) => !(state.isTable && !state.hasTextSelectionInTable),
|
|
206
231
|
isActive: (state) => state.isTable,
|
|
@@ -208,6 +233,8 @@ function MenuBar({
|
|
|
208
233
|
},
|
|
209
234
|
{
|
|
210
235
|
icon: <BorderAll />,
|
|
236
|
+
label: 'Table borders',
|
|
237
|
+
toggle: true,
|
|
211
238
|
onClick: (editor) => {
|
|
212
239
|
const tableAttrs = editor.getAttributes('table');
|
|
213
240
|
|
|
@@ -224,6 +251,8 @@ function MenuBar({
|
|
|
224
251
|
},
|
|
225
252
|
{
|
|
226
253
|
icon: <Bold />,
|
|
254
|
+
label: 'Bold',
|
|
255
|
+
toggle: true,
|
|
227
256
|
onClick: (editor) => editor.chain().focus().toggleBold().run(),
|
|
228
257
|
hidden: () => !activePlugins?.includes('bold'),
|
|
229
258
|
isActive: (state) => state.isBold,
|
|
@@ -231,6 +260,8 @@ function MenuBar({
|
|
|
231
260
|
},
|
|
232
261
|
{
|
|
233
262
|
icon: <Italic />,
|
|
263
|
+
label: 'Italic',
|
|
264
|
+
toggle: true,
|
|
234
265
|
onClick: (editor) => editor.chain().focus().toggleItalic().run(),
|
|
235
266
|
hidden: () => !activePlugins?.includes('italic'),
|
|
236
267
|
isActive: (state) => state.isItalic,
|
|
@@ -238,6 +269,8 @@ function MenuBar({
|
|
|
238
269
|
},
|
|
239
270
|
{
|
|
240
271
|
icon: <Strikethrough />,
|
|
272
|
+
label: 'Strikethrough',
|
|
273
|
+
toggle: true,
|
|
241
274
|
onClick: (editor) => editor.chain().focus().toggleStrike().run(),
|
|
242
275
|
hidden: () => !activePlugins?.includes('strikethrough'),
|
|
243
276
|
isActive: (state) => state.isStrike,
|
|
@@ -245,6 +278,8 @@ function MenuBar({
|
|
|
245
278
|
},
|
|
246
279
|
{
|
|
247
280
|
icon: <Code />,
|
|
281
|
+
label: 'Code',
|
|
282
|
+
toggle: true,
|
|
248
283
|
onClick: (editor) => editor.chain().focus().toggleCode().run(),
|
|
249
284
|
hidden: () => !activePlugins?.includes('code'),
|
|
250
285
|
isActive: (state) => state.isCode,
|
|
@@ -252,94 +287,118 @@ function MenuBar({
|
|
|
252
287
|
},
|
|
253
288
|
{
|
|
254
289
|
icon: <Underline />,
|
|
290
|
+
label: 'Underline',
|
|
291
|
+
toggle: true,
|
|
255
292
|
onClick: (editor) => editor.chain().focus().toggleUnderline().run(),
|
|
256
293
|
hidden: () => !activePlugins?.includes('underline'),
|
|
257
294
|
isActive: (state) => state.isUnderline,
|
|
258
295
|
},
|
|
259
296
|
{
|
|
260
297
|
icon: <SubscriptIcon />,
|
|
298
|
+
label: 'Subscript',
|
|
299
|
+
toggle: true,
|
|
261
300
|
onClick: (editor) => editor.chain().focus().toggleSubscript().run(),
|
|
262
301
|
hidden: () => !activePlugins?.includes('subscript'),
|
|
263
302
|
isActive: (state) => state.isSubScript,
|
|
264
303
|
},
|
|
265
304
|
{
|
|
266
305
|
icon: <SuperscriptIcon />,
|
|
306
|
+
label: 'Superscript',
|
|
307
|
+
toggle: true,
|
|
267
308
|
onClick: (editor) => editor.chain().focus().toggleSuperscript().run(),
|
|
268
309
|
hidden: () => !activePlugins?.includes('superscript'),
|
|
269
310
|
isActive: (state) => state.isSuperScript,
|
|
270
311
|
},
|
|
271
312
|
{
|
|
272
313
|
icon: <ImageIcon />,
|
|
314
|
+
label: 'Insert image',
|
|
273
315
|
hidden: () => !activePlugins?.includes('image'),
|
|
274
316
|
onClick: (editor) => editor.chain().focus().setImageUploadNode().run(),
|
|
275
317
|
},
|
|
276
318
|
{
|
|
277
319
|
icon: <TheatersIcon />,
|
|
320
|
+
label: 'Insert video',
|
|
278
321
|
hidden: () => !activePlugins?.includes('video'),
|
|
279
322
|
onClick: (editor) => editor.chain().focus().insertMedia({ type: 'video' }).run(),
|
|
280
323
|
},
|
|
281
324
|
{
|
|
282
325
|
icon: <VolumeUpIcon />,
|
|
326
|
+
label: 'Insert audio',
|
|
283
327
|
hidden: () => !activePlugins?.includes('audio'),
|
|
284
328
|
onClick: (editor) => editor.chain().focus().insertMedia({ type: 'audio', tag: 'audio' }).run(),
|
|
285
329
|
},
|
|
286
330
|
{
|
|
287
331
|
icon: <CSSIcon />,
|
|
332
|
+
label: 'CSS classes',
|
|
288
333
|
hidden: () => !activePlugins?.includes('css'),
|
|
289
334
|
onClick: (editor) => editor.commands.openCSSClassDialog(),
|
|
290
335
|
},
|
|
291
336
|
{
|
|
292
337
|
icon: <FormatQuote />,
|
|
338
|
+
label: 'Blockquote',
|
|
339
|
+
toggle: true,
|
|
293
340
|
hidden: () => !activePlugins?.includes('blockquote'),
|
|
294
341
|
onClick: (editor) => editor.chain().focus().toggleBlockquote().run(),
|
|
295
342
|
isActive: (state) => state.isBlockquote,
|
|
296
343
|
},
|
|
297
344
|
{
|
|
298
345
|
icon: <HeadingIcon />,
|
|
346
|
+
label: 'Heading',
|
|
347
|
+
toggle: true,
|
|
299
348
|
hidden: () => !activePlugins?.includes('h3'),
|
|
300
349
|
onClick: (editor) => editor.chain().focus().toggleHeadingParagraph().run(),
|
|
301
350
|
isActive: (state) => state.isHeadingParagraph,
|
|
302
351
|
},
|
|
303
352
|
{
|
|
304
353
|
icon: <Functions />,
|
|
354
|
+
label: 'Insert math',
|
|
305
355
|
hidden: () => !activePlugins?.includes('math'),
|
|
306
356
|
onClick: (editor) => editor.chain().focus().insertMath('').run(),
|
|
307
357
|
},
|
|
308
358
|
{
|
|
309
359
|
icon: <CharacterIcon letter="ñ" />,
|
|
360
|
+
label: 'Spanish characters (ñ)',
|
|
310
361
|
hidden: () => !activePlugins?.includes('languageCharacters'),
|
|
311
362
|
onClick: () => setShowPicker(spanishConfig),
|
|
312
363
|
},
|
|
313
364
|
{
|
|
314
365
|
icon: <CharacterIcon letter="€" />,
|
|
366
|
+
label: 'Special characters (€)',
|
|
315
367
|
hidden: () => activePlugins?.filter((p) => p === 'languageCharacters').length !== 2,
|
|
316
368
|
onClick: () => setShowPicker(specialConfig),
|
|
317
369
|
},
|
|
318
370
|
{
|
|
319
371
|
icon: <TextAlignIcon editor={editor} />,
|
|
372
|
+
label: 'Text alignment',
|
|
320
373
|
hidden: () => !activePlugins?.includes('text-align'),
|
|
321
374
|
onClick: () => {},
|
|
322
375
|
},
|
|
323
376
|
{
|
|
324
377
|
icon: <BulletedListIcon />,
|
|
378
|
+
label: 'Bulleted list',
|
|
379
|
+
toggle: true,
|
|
325
380
|
hidden: () => !activePlugins?.includes('bulleted-list'),
|
|
326
381
|
onClick: (editor) => editor.chain().focus().toggleBulletList().run(),
|
|
327
382
|
isActive: (state) => state.isBulletList,
|
|
328
383
|
},
|
|
329
384
|
{
|
|
330
385
|
icon: <NumberedListIcon />,
|
|
386
|
+
label: 'Numbered list',
|
|
387
|
+
toggle: true,
|
|
331
388
|
hidden: () => !activePlugins?.includes('numbered-list'),
|
|
332
389
|
onClick: (editor) => editor.chain().focus().toggleOrderedList().run(),
|
|
333
390
|
isActive: (state) => state.isOrderedList,
|
|
334
391
|
},
|
|
335
392
|
{
|
|
336
393
|
icon: <Undo />,
|
|
394
|
+
label: 'Undo',
|
|
337
395
|
hidden: () => !activePlugins?.includes('undo'),
|
|
338
396
|
onClick: (editor) => editor.chain().focus().undo().run(),
|
|
339
397
|
isDisabled: (state) => !state.canUndo,
|
|
340
398
|
},
|
|
341
399
|
{
|
|
342
400
|
icon: <Redo />,
|
|
401
|
+
label: 'Redo',
|
|
343
402
|
hidden: () => !activePlugins?.includes('redo'),
|
|
344
403
|
onClick: (editor) => editor.chain().focus().redo().run(),
|
|
345
404
|
isDisabled: (state) => !state.canRedo,
|
|
@@ -365,6 +424,7 @@ function MenuBar({
|
|
|
365
424
|
onChange?.(editor.getHTML());
|
|
366
425
|
}}
|
|
367
426
|
aria-label="Delete response area"
|
|
427
|
+
title="Delete response area"
|
|
368
428
|
>
|
|
369
429
|
<Delete />
|
|
370
430
|
</button>
|
|
@@ -383,7 +443,11 @@ function MenuBar({
|
|
|
383
443
|
return (
|
|
384
444
|
<button
|
|
385
445
|
key={index}
|
|
446
|
+
type="button"
|
|
386
447
|
disabled={disabled}
|
|
448
|
+
aria-label={btn.label}
|
|
449
|
+
title={btn.label}
|
|
450
|
+
aria-pressed={btn.toggle ? !!active : undefined}
|
|
387
451
|
onClick={(e) => {
|
|
388
452
|
e.preventDefault();
|
|
389
453
|
btn.onClick(editor);
|
|
@@ -397,6 +461,9 @@ function MenuBar({
|
|
|
397
461
|
</div>
|
|
398
462
|
{activePlugins?.includes('responseArea') && (
|
|
399
463
|
<button
|
|
464
|
+
type="button"
|
|
465
|
+
aria-label="Insert response area"
|
|
466
|
+
title="Insert response area"
|
|
400
467
|
onClick={() => {
|
|
401
468
|
editor.chain().focus().insertResponseArea(responseAreaProps.type).run();
|
|
402
469
|
}}
|
|
@@ -462,7 +529,7 @@ const StyledMenuBar = (props) => {
|
|
|
462
529
|
);
|
|
463
530
|
};
|
|
464
531
|
|
|
465
|
-
const StyledMenuBarRoot = styled('div')((
|
|
532
|
+
const StyledMenuBarRoot = styled('div')(() => ({
|
|
466
533
|
'& .defaultToolbar': {
|
|
467
534
|
display: 'flex',
|
|
468
535
|
width: '100%',
|
|
@@ -486,20 +553,26 @@ const StyledMenuBarRoot = styled('div')(({ theme }) => ({
|
|
|
486
553
|
height: '24px',
|
|
487
554
|
},
|
|
488
555
|
'&:hover': {
|
|
489
|
-
color:
|
|
556
|
+
color: color.text(),
|
|
490
557
|
},
|
|
491
558
|
'&:focus': {
|
|
492
|
-
|
|
559
|
+
/*
|
|
560
|
+
* A focus ring is the one thing a keyboard user cannot do without, so it takes
|
|
561
|
+
* the registered focus token rather than a grey. `--pie-button-focus-outline`
|
|
562
|
+
* measures at least 3.68:1 against every scheme's own background; MUI's
|
|
563
|
+
* grey[700] fell to 1.28:1 on yellow-on-navy.
|
|
564
|
+
*/
|
|
565
|
+
outline: `2px solid ${color.buttonFocusOutline()}`,
|
|
493
566
|
},
|
|
494
567
|
},
|
|
495
568
|
'& .active': {
|
|
496
|
-
color:
|
|
569
|
+
color: color.text(),
|
|
497
570
|
},
|
|
498
571
|
'& .disabled': {
|
|
499
572
|
opacity: 0.7,
|
|
500
573
|
cursor: 'not-allowed',
|
|
501
574
|
'& :hover': {
|
|
502
|
-
color:
|
|
575
|
+
color: color.disabled(),
|
|
503
576
|
},
|
|
504
577
|
},
|
|
505
578
|
'& .isActive': {
|
|
@@ -2,21 +2,27 @@ import React from 'react';
|
|
|
2
2
|
import { fireEvent, render } from '@testing-library/react';
|
|
3
3
|
import StyledMenuBar from '../MenuBar';
|
|
4
4
|
|
|
5
|
+
// flipped to false by the accessibility tests to render the toolbar in its disabled state
|
|
6
|
+
let mockCanRunCommands = true;
|
|
7
|
+
// node/mark types the accessibility tests want the editor state to report as active
|
|
8
|
+
let mockActiveTypes = [];
|
|
9
|
+
|
|
5
10
|
jest.mock('@tiptap/react', () => ({
|
|
6
11
|
EditorContent: ({ editor }) => <div data-testid="editor-content" />,
|
|
7
12
|
useEditorState: ({ selector }) => {
|
|
13
|
+
const can = () => ({ run: jest.fn(() => mockCanRunCommands) });
|
|
8
14
|
const mockEditor = {
|
|
9
|
-
isActive: jest.fn(() =>
|
|
15
|
+
isActive: jest.fn((name) => mockActiveTypes.includes(name)),
|
|
10
16
|
can: jest.fn(() => ({
|
|
11
17
|
chain: jest.fn(() => ({
|
|
12
|
-
toggleBold:
|
|
13
|
-
insertTable:
|
|
14
|
-
toggleItalic:
|
|
15
|
-
toggleStrike:
|
|
16
|
-
toggleCode:
|
|
17
|
-
unsetAllMarks:
|
|
18
|
-
undo:
|
|
19
|
-
redo:
|
|
18
|
+
toggleBold: can,
|
|
19
|
+
insertTable: can,
|
|
20
|
+
toggleItalic: can,
|
|
21
|
+
toggleStrike: can,
|
|
22
|
+
toggleCode: can,
|
|
23
|
+
unsetAllMarks: can,
|
|
24
|
+
undo: can,
|
|
25
|
+
redo: can,
|
|
20
26
|
})),
|
|
21
27
|
})),
|
|
22
28
|
getAttributes: jest.fn(() => ({ border: '1' })),
|
|
@@ -247,4 +253,86 @@ describe('StyledMenuBar', () => {
|
|
|
247
253
|
const { container } = render(<StyledMenuBar {...defaultProps} activePlugins={['table', 'bold', 'italic']} />);
|
|
248
254
|
expect(container).toBeInTheDocument();
|
|
249
255
|
});
|
|
256
|
+
describe('accessible names', () => {
|
|
257
|
+
const allPlugins = [
|
|
258
|
+
'table',
|
|
259
|
+
'bold',
|
|
260
|
+
'italic',
|
|
261
|
+
'strikethrough',
|
|
262
|
+
'code',
|
|
263
|
+
'underline',
|
|
264
|
+
'subscript',
|
|
265
|
+
'superscript',
|
|
266
|
+
'image',
|
|
267
|
+
'video',
|
|
268
|
+
'audio',
|
|
269
|
+
'css',
|
|
270
|
+
'blockquote',
|
|
271
|
+
'h3',
|
|
272
|
+
'math',
|
|
273
|
+
'languageCharacters',
|
|
274
|
+
'bulleted-list',
|
|
275
|
+
'numbered-list',
|
|
276
|
+
'undo',
|
|
277
|
+
'redo',
|
|
278
|
+
'responseArea',
|
|
279
|
+
];
|
|
280
|
+
|
|
281
|
+
const accessibleName = (button) => button.getAttribute('aria-label') || button.textContent.trim();
|
|
282
|
+
|
|
283
|
+
afterEach(() => {
|
|
284
|
+
mockCanRunCommands = true;
|
|
285
|
+
mockActiveTypes = [];
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
it('gives every toolbar button an accessible name', () => {
|
|
289
|
+
const { container } = render(<StyledMenuBar {...defaultProps} activePlugins={allPlugins} />);
|
|
290
|
+
const buttons = Array.from(container.querySelectorAll('button'));
|
|
291
|
+
|
|
292
|
+
expect(buttons.length).toBeGreaterThan(0);
|
|
293
|
+
buttons.forEach((button) => expect(accessibleName(button)).not.toEqual(''));
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
it('keeps accessible names on disabled buttons', () => {
|
|
297
|
+
mockCanRunCommands = false;
|
|
298
|
+
|
|
299
|
+
const { container } = render(<StyledMenuBar {...defaultProps} activePlugins={allPlugins} />);
|
|
300
|
+
const disabled = Array.from(container.querySelectorAll('button[disabled]'));
|
|
301
|
+
|
|
302
|
+
expect(disabled.length).toBeGreaterThan(0);
|
|
303
|
+
disabled.forEach((button) => expect(accessibleName(button)).not.toEqual(''));
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
it('labels the formatting and history buttons', () => {
|
|
307
|
+
const { getByLabelText } = render(<StyledMenuBar {...defaultProps} activePlugins={allPlugins} />);
|
|
308
|
+
|
|
309
|
+
['Bold', 'Italic', 'Underline', 'Insert table', 'Undo', 'Redo'].forEach((label) =>
|
|
310
|
+
expect(getByLabelText(label)).toBeInTheDocument(),
|
|
311
|
+
);
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
it('labels the table buttons', () => {
|
|
315
|
+
mockActiveTypes = ['table'];
|
|
316
|
+
|
|
317
|
+
const { getByLabelText } = render(<StyledMenuBar {...defaultProps} activePlugins={allPlugins} />);
|
|
318
|
+
|
|
319
|
+
['Add table row', 'Remove table row', 'Add table column', 'Remove table column', 'Remove table'].forEach(
|
|
320
|
+
(label) => expect(getByLabelText(label)).toBeInTheDocument(),
|
|
321
|
+
);
|
|
322
|
+
expect(getByLabelText('Table borders')).toHaveAttribute('aria-pressed', 'true');
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
it('labels the response area insert button', () => {
|
|
326
|
+
const { getByLabelText } = render(<StyledMenuBar {...defaultProps} activePlugins={['responseArea']} />);
|
|
327
|
+
expect(getByLabelText('Insert response area')).toBeInTheDocument();
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
it('exposes toggle state via aria-pressed on mark buttons', () => {
|
|
331
|
+
const { getByLabelText } = render(<StyledMenuBar {...defaultProps} activePlugins={allPlugins} />);
|
|
332
|
+
|
|
333
|
+
expect(getByLabelText('Bold')).toHaveAttribute('aria-pressed', 'false');
|
|
334
|
+
// one-shot actions are not toggles, so they must not report a pressed state
|
|
335
|
+
expect(getByLabelText('Undo')).not.toHaveAttribute('aria-pressed');
|
|
336
|
+
});
|
|
337
|
+
});
|
|
250
338
|
});
|
|
@@ -1,7 +1,20 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { fireEvent, render } from '@testing-library/react';
|
|
3
|
+
import { color } from '@pie-lib/render-ui';
|
|
3
4
|
import { Button, MarkButton, RawButton, RawMarkButton } from '../common/toolbar-buttons';
|
|
4
5
|
|
|
6
|
+
/*
|
|
7
|
+
* jsdom does not resolve custom properties, so toHaveStyle() normalises `var(--x, y)`
|
|
8
|
+
* to the empty string and cannot compare it. These buttons now take their ink from
|
|
9
|
+
* `--pie-text`, so assert against the rule emotion emitted for the element's class.
|
|
10
|
+
*/
|
|
11
|
+
const declaredColor = (el) =>
|
|
12
|
+
Array.from(document.querySelectorAll('style'))
|
|
13
|
+
.flatMap((tag) => Array.from(tag.sheet?.cssRules || []))
|
|
14
|
+
.filter((rule) => Array.from(el.classList).some((c) => rule.selectorText?.includes(`.${c}`)))
|
|
15
|
+
.map((rule) => rule.style?.color)
|
|
16
|
+
.find(Boolean);
|
|
17
|
+
|
|
5
18
|
describe('RawButton', () => {
|
|
6
19
|
it('renders without crashing', () => {
|
|
7
20
|
const { container } = render(<RawButton onClick={jest.fn()}>Click me</RawButton>);
|
|
@@ -58,7 +71,7 @@ describe('RawButton', () => {
|
|
|
58
71
|
</RawButton>,
|
|
59
72
|
);
|
|
60
73
|
const button = getByText('Active');
|
|
61
|
-
expect(button).
|
|
74
|
+
expect(declaredColor(button)).toBe(color.text());
|
|
62
75
|
});
|
|
63
76
|
|
|
64
77
|
it('applies disabled style when disabled is true', () => {
|
|
@@ -196,7 +209,7 @@ describe('RawMarkButton', () => {
|
|
|
196
209
|
</RawMarkButton>,
|
|
197
210
|
);
|
|
198
211
|
const button = getByText('B');
|
|
199
|
-
expect(button).
|
|
212
|
+
expect(declaredColor(button)).toBe(color.text());
|
|
200
213
|
});
|
|
201
214
|
|
|
202
215
|
it('has aria-label from label prop', () => {
|
|
@@ -2,11 +2,12 @@ import React from 'react';
|
|
|
2
2
|
import debug from 'debug';
|
|
3
3
|
import { styled } from '@mui/material/styles';
|
|
4
4
|
import PropTypes from 'prop-types';
|
|
5
|
+
import { color } from '@pie-lib/render-ui';
|
|
5
6
|
|
|
6
7
|
const StyledButton = styled('button', {
|
|
7
8
|
shouldForwardProp: (prop) => !['active', 'disabled', 'extraStyles'].includes(prop),
|
|
8
|
-
})(({
|
|
9
|
-
color: active ?
|
|
9
|
+
})(({ active, disabled }) => ({
|
|
10
|
+
color: active ? color.text() : color.disabled(),
|
|
10
11
|
display: 'inline-flex',
|
|
11
12
|
padding: '2px',
|
|
12
13
|
background: 'none',
|
|
@@ -18,10 +19,11 @@ const StyledButton = styled('button', {
|
|
|
18
19
|
height: '24px',
|
|
19
20
|
},
|
|
20
21
|
'&:hover': {
|
|
21
|
-
color: disabled ?
|
|
22
|
+
color: disabled ? color.disabled() : color.text(),
|
|
22
23
|
},
|
|
23
24
|
'&:focus': {
|
|
24
|
-
|
|
25
|
+
// See MenuBar: the focus ring takes the registered focus token, not a grey.
|
|
26
|
+
outline: `2px solid ${color.buttonFocusOutline()}`,
|
|
25
27
|
},
|
|
26
28
|
...(disabled && {
|
|
27
29
|
opacity: 0.7,
|
|
@@ -10,7 +10,15 @@ const StyledPath = styled('path')({
|
|
|
10
10
|
|
|
11
11
|
const SvgIcon = (Component) => {
|
|
12
12
|
return (props) => (
|
|
13
|
-
<svg
|
|
13
|
+
<svg
|
|
14
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
15
|
+
version="1.1"
|
|
16
|
+
width="24"
|
|
17
|
+
height="24"
|
|
18
|
+
viewBox="0 0 24 24"
|
|
19
|
+
aria-hidden="true"
|
|
20
|
+
focusable="false"
|
|
21
|
+
>
|
|
14
22
|
<Component {...props} />
|
|
15
23
|
</svg>
|
|
16
24
|
);
|