diff --git a/demo/scripts/controlsV2/demoButtons/tableTitleButton.ts b/demo/scripts/controlsV2/demoButtons/tableTitleButton.ts new file mode 100644 index 00000000000..dbe210b4f06 --- /dev/null +++ b/demo/scripts/controlsV2/demoButtons/tableTitleButton.ts @@ -0,0 +1,50 @@ +import { setTableTitle } from 'roosterjs-content-model-api'; +import { showInputDialog } from 'roosterjs-react'; +import type { RibbonButton } from 'roosterjs-react'; + +/** + * Key of localized strings of Table title button + */ +export type TableTitleButtonStringKey = 'buttonTableTitle'; + +/** + * "Table Title" button on the format ribbon + */ +export const tableTitleButton: RibbonButton = { + key: 'buttonTableTitle', + unlocalizedText: 'Add Table Title', + iconName: 'OpenInNewWindow', + flipWhenRtl: true, + onClick: (editor, key, strings, uiUtility) => { + showInputDialog( + uiUtility, + key, + 'Add table title', + { + title: { + labelKey: null, + unlocalizedLabel: null, + initValue: '', + }, + description: { + labelKey: null, + unlocalizedLabel: null, + initValue: '', + }, + }, + strings, + (itemName, newValue, values) => { + if (itemName == 'title') { + values.title = newValue; + } + if (itemName == 'description') { + values.description = newValue; + } + return values; + } + ).then(values => { + editor.focus(); + setTableTitle(editor, values.title || ''); + }); + }, +}; diff --git a/demo/scripts/controlsV2/sidePane/contentModel/components/format/formatPart/TitleFormatRenderer.ts b/demo/scripts/controlsV2/sidePane/contentModel/components/format/formatPart/TitleFormatRenderer.ts new file mode 100644 index 00000000000..a404a43f900 --- /dev/null +++ b/demo/scripts/controlsV2/sidePane/contentModel/components/format/formatPart/TitleFormatRenderer.ts @@ -0,0 +1,11 @@ +import { createTextFormatRenderer } from '../utils/createTextFormatRenderer'; +import { FormatRenderer } from '../utils/FormatRenderer'; +import { TitleFormat } from 'roosterjs-content-model-types'; + +export const TitleFormatRenderer: FormatRenderer = createTextFormatRenderer< + TitleFormat +>( + 'Title', + format => format.title, + (format, value) => (format.title = value) +); diff --git a/demo/scripts/controlsV2/sidePane/contentModel/components/model/ContentModelTableView.tsx b/demo/scripts/controlsV2/sidePane/contentModel/components/model/ContentModelTableView.tsx index 41394a16d0e..4aefcec522f 100644 --- a/demo/scripts/controlsV2/sidePane/contentModel/components/model/ContentModelTableView.tsx +++ b/demo/scripts/controlsV2/sidePane/contentModel/components/model/ContentModelTableView.tsx @@ -15,6 +15,7 @@ import { MetadataView } from '../format/MetadataView'; import { SpacingFormatRenderer } from '../format/formatPart/SpacingFormatRenderer'; import { TableLayoutFormatRenderer } from '../format/formatPart/TableLayoutFormatRenderer'; import { TableMetadataFormatRenders } from '../format/formatPart/TableMetadataFormatRenders'; +import { TitleFormatRenderer } from '../format/formatPart/TitleFormatRenderer'; import { useProperty } from '../../hooks/useProperty'; const styles = require('./ContentModelTableView.scss'); @@ -28,6 +29,7 @@ const TableFormatRenderers: FormatRenderer[] = [ BorderBoxFormatRenderer, DisplayFormatRenderer, TableLayoutFormatRenderer, + TitleFormatRenderer, ]; export function ContentModelTableView(props: { table: ContentModelTable }) { diff --git a/demo/scripts/controlsV2/tabs/ribbonButtons.ts b/demo/scripts/controlsV2/tabs/ribbonButtons.ts index 6911f0aa9c5..374d548770b 100644 --- a/demo/scripts/controlsV2/tabs/ribbonButtons.ts +++ b/demo/scripts/controlsV2/tabs/ribbonButtons.ts @@ -21,6 +21,7 @@ import { tableBorderColorButton } from '../demoButtons/tableBorderColorButton'; import { tableBorderStyleButton } from '../demoButtons/tableBorderStyleButton'; import { tableBorderWidthButton } from '../demoButtons/tableBorderWidthButton'; import { tableOptionsButton } from '../demoButtons/tableOptionsButton'; +import { tableTitleButton } from '../demoButtons/tableTitleButton'; import { tabNames } from './getTabs'; import { tableAlignCellButton, @@ -83,6 +84,7 @@ const tableButtons: RibbonButton[] = [ insertTableButton, formatTableButton, setTableCellShadeButton, + tableTitleButton, tableOptionsButton, tableInsertButton, tableDeleteButton, @@ -184,6 +186,7 @@ const allButtons: RibbonButton[] = [ tableBorderColorButton, tableBorderWidthButton, tableBorderStyleButton, + tableTitleButton, imageBorderColorButton, imageBorderWidthButton, imageBorderStyleButton, diff --git a/packages/roosterjs-content-model-api/lib/index.ts b/packages/roosterjs-content-model-api/lib/index.ts index 292f44d08e5..102fc93066d 100644 --- a/packages/roosterjs-content-model-api/lib/index.ts +++ b/packages/roosterjs-content-model-api/lib/index.ts @@ -3,6 +3,7 @@ export { formatTable } from './publicApi/table/formatTable'; export { setTableCellShade } from './publicApi/table/setTableCellShade'; export { editTable } from './publicApi/table/editTable'; export { applyTableBorderFormat } from './publicApi/table/applyTableBorderFormat'; +export { setTableTitle } from './publicApi/table/setTableTitle'; export { toggleBullet } from './publicApi/list/toggleBullet'; export { toggleNumbering } from './publicApi/list/toggleNumbering'; export { toggleBold } from './publicApi/segment/toggleBold'; diff --git a/packages/roosterjs-content-model-api/lib/publicApi/table/setTableTitle.ts b/packages/roosterjs-content-model-api/lib/publicApi/table/setTableTitle.ts new file mode 100644 index 00000000000..fd0e4033d0b --- /dev/null +++ b/packages/roosterjs-content-model-api/lib/publicApi/table/setTableTitle.ts @@ -0,0 +1,28 @@ +import { getFirstSelectedTable, mutateBlock } from 'roosterjs-content-model-dom'; +import type { IEditor } from 'roosterjs-content-model-types'; + +/** + * Set table title + * @param editor The editor instance + * @param title The title to set + */ +export function setTableTitle(editor: IEditor, title: string) { + editor.focus(); + + editor.formatContentModel( + model => { + const [table] = getFirstSelectedTable(model); + + if (table) { + mutateBlock(table).format.title = title; + + return true; + } else { + return false; + } + }, + { + apiName: 'setTableTitle', + } + ); +} diff --git a/packages/roosterjs-content-model-dom/lib/formatHandlers/common/titleFormatHandler.ts b/packages/roosterjs-content-model-dom/lib/formatHandlers/common/titleFormatHandler.ts new file mode 100644 index 00000000000..47c8abcde2c --- /dev/null +++ b/packages/roosterjs-content-model-dom/lib/formatHandlers/common/titleFormatHandler.ts @@ -0,0 +1,18 @@ +import type { FormatHandler } from '../FormatHandler'; +import type { TitleFormat } from 'roosterjs-content-model-types'; + +/** + * @internal + */ +export const titleFormatHandler: FormatHandler = { + parse: (format, element) => { + if (element.title) { + format.title = element.title; + } + }, + apply: (format, element) => { + if (format.title) { + element.title = format.title; + } + }, +}; diff --git a/packages/roosterjs-content-model-dom/lib/formatHandlers/defaultFormatHandlers.ts b/packages/roosterjs-content-model-dom/lib/formatHandlers/defaultFormatHandlers.ts index 4d2ac993d31..918bfd70b2a 100644 --- a/packages/roosterjs-content-model-dom/lib/formatHandlers/defaultFormatHandlers.ts +++ b/packages/roosterjs-content-model-dom/lib/formatHandlers/defaultFormatHandlers.ts @@ -31,6 +31,7 @@ import { textAlignFormatHandler } from './block/textAlignFormatHandler'; import { textColorFormatHandler } from './segment/textColorFormatHandler'; import { textColorOnTableCellFormatHandler } from './table/textColorOnTableCellFormatHandler'; import { textIndentFormatHandler } from './block/textIndentFormatHandler'; +import { titleFormatHandler } from './common/titleFormatHandler'; import { underlineFormatHandler } from './segment/underlineFormatHandler'; import { verticalAlignFormatHandler } from './common/verticalAlignFormatHandler'; import { whiteSpaceFormatHandler } from './block/whiteSpaceFormatHandler'; @@ -83,6 +84,7 @@ const defaultFormatHandlerMap: FormatHandlers = { textColor: textColorFormatHandler, textColorOnTableCell: textColorOnTableCellFormatHandler, textIndent: textIndentFormatHandler, + title: titleFormatHandler, underline: underlineFormatHandler, verticalAlign: verticalAlignFormatHandler, whiteSpace: whiteSpaceFormatHandler, @@ -171,6 +173,7 @@ export const defaultFormatKeysPerCategory: { 'size', 'tableLayout', 'textColor', + 'title', ], tableBorder: ['borderBox', 'tableSpacing'], tableCellBorder: ['borderBox'], diff --git a/packages/roosterjs-content-model-types/lib/contentModel/format/ContentModelTableFormat.ts b/packages/roosterjs-content-model-types/lib/contentModel/format/ContentModelTableFormat.ts index 7d84b3fe376..6686348950f 100644 --- a/packages/roosterjs-content-model-types/lib/contentModel/format/ContentModelTableFormat.ts +++ b/packages/roosterjs-content-model-types/lib/contentModel/format/ContentModelTableFormat.ts @@ -1,3 +1,5 @@ +import { AriaDescribedByFormat } from 'roosterjs/lib'; +import { TitleFormat } from './formatParts/TitleFormat'; import type { BorderBoxFormat } from './formatParts/BorderBoxFormat'; import type { BorderFormat } from './formatParts/BorderFormat'; import type { ContentModelBlockFormat } from './ContentModelBlockFormat'; @@ -19,4 +21,6 @@ export type ContentModelTableFormat = ContentModelBlockFormat & MarginFormat & DisplayFormat & TableLayoutFormat & - SizeFormat; + SizeFormat & + TitleFormat & + AriaDescribedByFormat; diff --git a/packages/roosterjs-content-model-types/lib/contentModel/format/FormatHandlerTypeMap.ts b/packages/roosterjs-content-model-types/lib/contentModel/format/FormatHandlerTypeMap.ts index d8c851be670..0409101e5c9 100644 --- a/packages/roosterjs-content-model-types/lib/contentModel/format/FormatHandlerTypeMap.ts +++ b/packages/roosterjs-content-model-types/lib/contentModel/format/FormatHandlerTypeMap.ts @@ -12,6 +12,7 @@ import type { FontFamilyFormat } from './formatParts/FontFamilyFormat'; import type { FontSizeFormat } from './formatParts/FontSizeFormat'; import type { HtmlAlignFormat } from './formatParts/HtmlAlignFormat'; import type { IdFormat } from './formatParts/IdFormat'; +import type { TitleFormat } from './formatParts/TitleFormat'; import type { ItalicFormat } from './formatParts/ItalicFormat'; import type { LetterSpacingFormat } from './formatParts/LetterSpacingFormat'; import type { LineHeightFormat } from './formatParts/LineHeightFormat'; @@ -197,6 +198,11 @@ export interface FormatHandlerTypeMap { */ textIndent: TextIndentFormat; + /** + * Format for TitleFormat + */ + title: TitleFormat; + /** * Format for UnderlineFormat */ diff --git a/packages/roosterjs-content-model-types/lib/contentModel/format/formatParts/TitleFormat.ts b/packages/roosterjs-content-model-types/lib/contentModel/format/formatParts/TitleFormat.ts new file mode 100644 index 00000000000..59ea5a9a581 --- /dev/null +++ b/packages/roosterjs-content-model-types/lib/contentModel/format/formatParts/TitleFormat.ts @@ -0,0 +1,9 @@ +/** + * Format for element with Title + */ +export type TitleFormat = { + /** + * Title of the element + */ + title?: string; +}; diff --git a/packages/roosterjs-content-model-types/lib/index.ts b/packages/roosterjs-content-model-types/lib/index.ts index 06720540376..b8325f68900 100644 --- a/packages/roosterjs-content-model-types/lib/index.ts +++ b/packages/roosterjs-content-model-types/lib/index.ts @@ -47,6 +47,7 @@ export { TextIndentFormat } from './contentModel/format/formatParts/TextIndentFo export { WhiteSpaceFormat } from './contentModel/format/formatParts/WhiteSpaceFormat'; export { DisplayFormat } from './contentModel/format/formatParts/DisplayFormat'; export { IdFormat } from './contentModel/format/formatParts/IdFormat'; +export { TitleFormat } from './contentModel/format/formatParts/TitleFormat'; export { SpacingFormat } from './contentModel/format/formatParts/SpacingFormat'; export { TableLayoutFormat } from './contentModel/format/formatParts/TableLayoutFormat'; export { LinkFormat } from './contentModel/format/formatParts/LinkFormat';