Backend API
<template>
<div>
<div class="widget-container">
<DxResizable
class="resizable-container"
area=".widget-container"
handles="right"
:min-width="500"
:min-height="150"
:max-height="370"
>
<DxToolbar :multiline="multiline">
<DxItem
location="before"
widget="dxButton"
>
<DxButton
icon="undo"
:styling-mode="stylingMode"
:on-click="onUndoButtonClick"
/>
</DxItem>
<DxItem
location="before"
widget="dxButton"
>
<DxButton
icon="redo"
:styling-mode="stylingMode"
:on-click="onRedoButtonClick"
/>
</DxItem>
<DxItem
location="before"
locate-in-menu="auto"
template="separatorTemplate"
menu-item-template="menuSeparatorTemplate"
/>
<DxItem
location="before"
locate-in-menu="auto"
>
<DxDropDownButton
width="100%"
display-expr="text"
key-expr="size"
item-template="fontSizeTemplate"
:styling-mode="stylingMode"
:use-select-mode="true"
:items="fontSizes"
:selected-item-key="fontSize"
:on-selection-changed="onFontSizeSelectionChanged"
>
<template #fontSizeTemplate="{ data }">
<div :style="{ fontSize: data.size + 'px' }">
{{ data.text }}
</div>
</template>
</DxDropDownButton>
</DxItem>
<DxItem
location="before"
locate-in-menu="auto"
>
<DxDropDownButton
width="100%"
icon="indent"
display-expr="text"
key-expr="lineHeight"
:styling-mode="stylingMode"
:use-select-mode="true"
:items="lineHeights"
:selected-item-key="lineHeight"
:on-selection-changed="onLineHeightSelectionChanged"
/>
</DxItem>
<DxItem
location="before"
locate-in-menu="auto"
>
<DxSelectBox
placeholder="Font"
display-expr="text"
:input-attr="{ 'aria-label': 'Font' }"
:data-source="fontFamilies"
:on-item-click="onFontFamilyClick"
/>
</DxItem>
<DxItem
location="before"
locate-in-menu="auto"
template="separatorTemplate"
menu-item-template="menuSeparatorTemplate"
/>
<DxItem location="before">
<DxButtonGroup
display-expr="text"
key-expr="icon"
styling-mode="outlined"
selection-mode="multiple"
:items="fontStyles"
:on-item-click="onFontStyleItemClick"
/>
</DxItem>
<DxItem
location="before"
template="separatorTemplate"
/>
<DxItem
location="before"
locate-in-menu="auto"
template="textAlignTemplate"
menu-item-template="textAlignMenuTemplate"
widget="dxButtonGroup"
/>
<DxItem location="before">
<DxButtonGroup
key-expr="alignment"
styling-mode="outlined"
:items="listTypes"
:on-item-click="onListTypeButtonClick"
/>
</DxItem>
<DxItem
location="before"
locate-in-menu="auto"
template="separatorTemplate"
menu-item-template="menuSeparatorTemplate"
/>
<DxItem
location="before"
locate-in-menu="auto"
>
<DxSelectBox
display-expr="text"
value-expr="text"
:input-attr="{ 'aria-label': 'Text Style' }"
:data-source="headings"
:value="heading"
:on-item-click="onHeadingClick"
/>
</DxItem>
<DxItem
location="before"
locate-in-menu="auto"
template="separatorTemplate"
menu-item-template="menuSeparatorTemplate"
/>
<DxItem
location="before"
locate-in-menu="auto"
show-text="inMenu"
widget="dxButton"
>
<DxButton
icon="link"
text="Link"
:styling-mode="stylingMode"
:on-click="onLinkButtonClick"
/>
</DxItem>
<DxItem
location="before"
locate-in-menu="auto"
show-text="inMenu"
widget="dxButton"
>
<DxButton
icon="image"
text="Add image"
:styling-mode="stylingMode"
:on-click="onAddImageButtonClick"
/>
</DxItem>
<DxItem
location="before"
locate-in-menu="auto"
template="separatorTemplate"
menu-item-template="menuSeparatorTemplate"
/>
<DxItem
location="before"
locate-in-menu="auto"
show-text="inMenu"
widget="dxButton"
>
<DxButton
icon="clearformat"
text="Clear formating"
:styling-mode="stylingMode"
:on-click="onClearButtonClick"
/>
</DxItem>
<DxItem
location="before"
locate-in-menu="auto"
show-text="inMenu"
widget="dxButton"
>
<DxButton
icon="codeblock"
text="Code block"
:styling-mode="stylingMode"
:on-click="onCodeBlockButtonClick"
/>
</DxItem>
<DxItem
location="before"
locate-in-menu="auto"
show-text="inMenu"
widget="dxButton"
>
<DxButton
icon="blockquote"
text="Blockquote"
:styling-mode="stylingMode"
:on-click="onQuoteButtonClick"
/>
</DxItem>
<DxItem
location="before"
locate-in-menu="auto"
template="separatorTemplate"
menu-item-template="menuSeparatorTemplate"
/>
<DxItem
location="after"
widget="dxButton"
show-text="inMenu"
>
<DxButton
icon="attach"
text="Attach"
:styling-mode="stylingMode"
:on-click="onAttachButtonClick"
/>
</DxItem>
<DxItem
locate-in-menu="always"
widget="dxButton"
show-text="inMenu"
>
<DxButton
icon="help"
text="About"
:styling-mode="stylingMode"
:on-click="onAboutButtonClick"
/>
</DxItem>
<template #separatorTemplate>
<div class="toolbar-separator"/>
</template>
<template #menuSeparatorTemplate>
<div class="toolbar-menu-separator"/>
</template>
<template #textAlignTemplate>
<DxButtonGroup
key-expr="alignment"
styling-mode="outlined"
:items="textAlignItems"
:selected-item-keys="textAlign"
@item-click="onTextAlignItemClick"
/>
</template>
<template #textAlignMenuTemplate>
<DxButtonGroup
:items="textAlignItemsExtended"
display-expr="text"
:selected-item-keys="textAlign"
key-expr="alignment"
styling-mode="outlined"
@item-click="onTextAlignItemClick"
/>
</template>
</DxToolbar>
</DxResizable>
</div>
<div class="options-container">
<div class="caption">Options</div>
<DxCheckBox
v-model="multiline"
text="Multiline mode"
/>
</div>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import DxCheckBox from 'devextreme-vue/check-box';
import DxToolbar, { DxItem } from 'devextreme-vue/toolbar';
import DxButton from 'devextreme-vue/button';
import DxButtonGroup, { type DxButtonGroupTypes } from 'devextreme-vue/button-group';
import DxResizable from 'devextreme-vue/resizable';
import DxDropDownButton from 'devextreme-vue/drop-down-button';
import DxSelectBox from 'devextreme-vue/select-box';
import themes from 'devextreme/ui/themes';
import notify from 'devextreme/ui/notify';
import {
fontSizes,
lineHeights,
fontFamilies,
fontStyles,
headings,
textAlignItems,
textAlignItemsExtended,
listTypes,
} from './data.ts';
import 'devextreme/ui/select_box';
const stylingMode = !themes.current().startsWith('generic') ? 'text' : undefined;
const lineHeightDefault = lineHeights[1].lineHeight;
const textAlignDefault = [textAlignItems[0].alignment];
const fontSizeDefault = fontSizes[2].size;
const headingDefault = headings[0].text;
const multiline = ref(true);
const lineHeight = ref(lineHeightDefault);
const textAlign = ref(textAlignDefault);
const fontSize = ref(fontSizeDefault);
const heading = ref(headingDefault);
function onTextAlignItemClick(e: DxButtonGroupTypes.ItemClickEvent) {
const { alignment, hint } = e.itemData;
textAlign.value = alignment;
onButtonClick(hint);
}
function onButtonClick(name: string) {
notify(`The "${name}" button has been clicked`);
}
function onUndoButtonClick() {
onButtonClick('Undo');
}
function onRedoButtonClick() {
onButtonClick('Redo');
}
function onFontStyleItemClick(e: DxButtonGroupTypes.ItemClickEvent) {
onButtonClick(e.itemData.hint);
}
function onListTypeButtonClick(e: DxButtonGroupTypes.ItemClickEvent) {
onButtonClick(e.itemData.hint);
}
function onLinkButtonClick() {
onButtonClick('Link');
}
function onAddImageButtonClick() {
onButtonClick('Add Image');
}
function onClearButtonClick() {
onButtonClick('Clear Formating');
}
function onCodeBlockButtonClick() {
onButtonClick('Code Block');
}
function onQuoteButtonClick() {
onButtonClick('Blockquote');
}
function onAttachButtonClick() {
onButtonClick('Attach');
}
function onAboutButtonClick() {
onButtonClick('About');
}
function onSelectionChanged(name: string) {
notify(`The "${name}" value has been changed`);
}
function onFontSizeSelectionChanged() {
onSelectionChanged('Font Size');
}
function onLineHeightSelectionChanged() {
onSelectionChanged('Line Height');
}
function onHeadingClick() {
notify('The "Heading" value has been changed');
}
function onFontFamilyClick() {
notify('The "Font Family" value has been changed');
}
</script>
<style>
.dx-resizable-handle::after {
content: "";
position: absolute;
width: 9px;
height: 36px;
border: none;
border-radius: 50px;
background-color: #fff;
box-shadow: 0 2px 6px 0 rgba(0, 0, 0, 0.24);
}
.dx-resizable-handle-right::after {
top: 50%;
right: -5px;
transform: translateY(-50%);
}
.dx-toolbar.dx-toolbar-multiline .dx-toolbar-item {
margin-bottom: 5px;
}
.widget-container {
margin-right: 10px;
}
.resizable-container {
padding: 10px;
height: 300px;
border: 1px dotted #999;
border-radius: 4px;
box-sizing: border-box;
}
.options-container {
margin-top: 20px;
padding: 20px;
background-color: rgba(191, 191, 191, 0.15);
position: relative;
}
.caption {
font-size: 18px;
font-weight: 500;
margin-bottom: 10px;
}
.toolbar-separator {
height: 36px;
margin: 0 5px;
border-left: 1px solid #ddd;
}
.toolbar-menu-separator {
height: 1px;
border-bottom: 1px solid #ddd;
}
.dx-button-content {
display: flex;
}
</style>
window.exports = window.exports || {};
window.config = {
transpiler: 'plugin-babel',
meta: {
'*.vue': {
loader: 'vue-loader',
},
'*.ts': {
loader: 'demo-ts-loader',
},
'*.svg': {
loader: 'svg-loader',
},
'devextreme/time_zone_utils.js': {
'esModule': true,
},
'devextreme/localization.js': {
'esModule': true,
},
'devextreme/viz/palette.js': {
'esModule': true,
},
'openai': {
'esModule': true,
},
},
paths: {
'project:': '../../../../',
'npm:': 'https://cdn.jsdelivr.net/npm/',
'bundles:': '../../../../bundles/',
'externals:': '../../../../bundles/externals/',
},
map: {
'vue': 'npm:vue@3.4.27/dist/vue.esm-browser.js',
'@vue/shared': 'npm:@vue/shared@3.4.27/dist/shared.cjs.prod.js',
'vue-loader': 'npm:dx-systemjs-vue-browser@1.1.2/index.js',
'demo-ts-loader': 'project:utils/demo-ts-loader.js',
'jszip': 'npm:jszip@3.10.1/dist/jszip.min.js',
'svg-loader': 'project:utils/svg-loader.js',
'mitt': 'npm:mitt/dist/mitt.umd.js',
'rrule': 'npm:rrule@2.6.4/dist/es5/rrule.js',
'luxon': 'npm:luxon@3.4.4/build/global/luxon.min.js',
'es6-object-assign': 'npm:es6-object-assign',
'devextreme': 'npm:devextreme@link:../../packages/devextreme/artifacts/npm/devextreme/cjs',
'devextreme-vue': 'npm:devextreme-vue@link:../../packages/devextreme-vue/npm/cjs',
'devextreme-quill': 'npm:devextreme-quill@1.7.6/dist/dx-quill.min.js',
'devexpress-diagram': 'npm:devexpress-diagram@2.2.24/dist/dx-diagram.js',
'devexpress-gantt': 'npm:devexpress-gantt@4.1.64/dist/dx-gantt.js',
'inferno': 'npm:inferno@8.2.3/dist/inferno.min.js',
'inferno-compat': 'npm:inferno-compat/dist/inferno-compat.min.js',
'inferno-create-element': 'npm:inferno-create-element@8.2.3/dist/inferno-create-element.min.js',
'inferno-dom': 'npm:inferno-dom/dist/inferno-dom.min.js',
'inferno-hydrate': 'npm:inferno-hydrate/dist/inferno-hydrate.min.js',
'inferno-clone-vnode': 'npm:inferno-clone-vnode/dist/inferno-clone-vnode.min.js',
'inferno-create-class': 'npm:inferno-create-class/dist/inferno-create-class.min.js',
'inferno-extras': 'npm:inferno-extras/dist/inferno-extras.min.js',
'@preact/signals-core': 'npm:@preact/signals-core@1.8.0/dist/signals-core.min.js',
'plugin-babel': 'npm:systemjs-plugin-babel@0.0.25/plugin-babel.js',
'systemjs-babel-build': 'npm:systemjs-plugin-babel@0.0.25/systemjs-babel-browser.js',
// Prettier
'prettier/standalone': 'npm:prettier@2.8.8/standalone.js',
'prettier/parser-html': 'npm:prettier@2.8.8/parser-html.js',
},
packages: {
'devextreme-vue': {
main: 'index.js',
},
'devextreme-vue/common': {
main: 'index.js',
},
'devextreme': {
defaultExtension: 'js',
},
'devextreme/events/utils': {
main: 'index',
},
'devextreme/common/core/events/utils': {
main: 'index',
},
'devextreme/events': {
main: 'index',
},
'es6-object-assign': {
main: './index.js',
defaultExtension: 'js',
},
},
packageConfigPaths: [
'npm:@devextreme/*/package.json',
],
babelOptions: {
sourceMaps: false,
stage0: true,
},
};
System.config(window.config);
// eslint-disable-next-line
const useTgzInCSB = ['openai'];
export const fontSizes = [
{ size: 10, text: '10px' },
{ size: 12, text: '12px' },
{ size: 14, text: '14px' },
{ size: 16, text: '16px' },
{ size: 18, text: '18px' },
];
export const lineHeights = [
{ lineHeight: 1, text: '1' },
{ lineHeight: 1.35, text: '1.35' },
{ lineHeight: 1.5, text: '1.5' },
{ lineHeight: 2, text: '2' },
];
export const fontFamilies = [
{ text: 'Arial' },
{ text: 'Courier New' },
{ text: 'Georgia' },
{ text: 'Impact' },
{ text: 'Lucida Console' },
{ text: 'Tahoma' },
{ text: 'Times New Roman' },
];
export const headings = [
{ text: 'Normal text' },
{ text: 'Heading 1' },
{ text: 'Heading 2' },
{ text: 'Heading 3' },
{ text: 'Heading 4' },
{ text: 'Heading 5' },
];
export const fontStyles = [
{
icon: 'bold',
hint: 'Bold',
},
{
icon: 'italic',
hint: 'Italic',
},
{
icon: 'underline',
hint: 'Underlined',
},
{
icon: 'strike',
hint: 'Strikethrough',
},
];
export const textAlignItemsExtended = [
{
icon: 'alignleft',
alignment: 'left',
hint: 'Align Left',
text: 'Align left',
},
{
icon: 'aligncenter',
alignment: 'center',
hint: 'Center',
text: 'Center',
},
{
icon: 'alignright',
alignment: 'right',
hint: 'Align Right',
text: 'Align right',
},
{
icon: 'alignjustify',
alignment: 'justify',
hint: 'Justify',
text: 'Justify',
},
];
export const textAlignItems = textAlignItemsExtended.map((item) => {
const { icon, alignment, hint } = item;
return {
icon,
alignment,
hint,
};
});
export const listTypes = [
{
icon: 'orderedlist',
alignment: 'orderedlist',
hint: 'Ordered',
},
{
icon: 'bulletlist',
alignment: 'bulletlist',
hint: 'Bullet',
},
];
import { createApp } from 'vue';
import App from './App.vue';
createApp(App).mount('#app');
<!DOCTYPE html>
<html lang="en">
<head>
<title>DevExtreme Demo</title>
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=5.0" />
<link rel="stylesheet" type="text/css" href="https://cdn3.devexpress.com/jslib/25.1.7/css/dx.light.css" />
<script src="https://cdn.jsdelivr.net/npm/typescript@5.4.5/lib/typescript.js"></script>
<script type="module">
import * as vueCompilerSFC from "https://cdn.jsdelivr.net/npm/@vue/compiler-sfc@3.4.27/dist/compiler-sfc.esm-browser.js";
window.vueCompilerSFC = vueCompilerSFC;
</script>
<script src="https://cdn.jsdelivr.net/npm/core-js@2.6.12/client/shim.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/systemjs@0.21.3/dist/system.js"></script>
<script type="text/javascript" src="config.js"></script>
<script type="text/javascript">
System.import("./index.ts");
</script>
</head>
<body class="dx-viewport">
<div class="demo-container">
<div id="app"> </div>
</div>
</body>
</html>
In this demo, you can drag our Toolbar container's resizing handle to visualize resize operations and view our rendering implementation on different screens. When used in single-line mode (default), our Toolbar does not wrap content and displays an overflow menu for items that do not fit within the container. Use the locateInMenu property to control whether items appear in the overflow menu.
If Toolbar width exceeds container width (and if you enable the component’s multiline property), our Toolbar wraps content across multiple lines.