Migration guide
The migration guide includes instructions on how to migrate to the latest major version, with detailed descriptions of the breaking changes that come with the release. See the changelog for all the changes.
v4.0 (July 3, 2024)
Version 4.0 should not have significant breaking changes for most users. The main major changes are the
removal of LightningCharts, value setting in Combobox and font adjustments. 4.0 introduces two new
components, mo-context-menu
and
mo-kbd
. These components are often used together with the improved
mo-menu
that now has functionality for including submenus.
The documentation tool for Modes UI has been changed from Docsify to Eleventy, and the design has received a major facelift. A new documentation template repository has been established and templates are now possible to add to the Modes UI documentation.
Breaking changes
LightningChart based components have been removed. Combobox received an overhaul in the way it handles programmatic value setting, which may break existing behavior in projects. The GT-Eesti font is now defined slightly differently. Find more information below.
Dependencies
There are a number of dependency upgrades and changes in this version, but few are relevant to end-users of the library. Many devDependencies have been upgraded or changed, see below for the most important changes, and #221 for all of the changes.
LightningCharts have been separated into their own library. See below for more information.
- ”@arction/lcjs”: ”^5.2.0″- ”@arction/lcjs-themes”: ”^3.2.1″
The react wrapping utility library from Lit has been migrated to using the new stable release
- ”@lit-labs/react”: ”^1.0.2″- + ”@lit/react”: ”^1.0.0″
@lit/react
is now an external dependency. If you are using the React-wrapped components,
you must install it separately using npm install @lit/react
.
Bundling
The library package is now available in two different options. The previously familiar
/dist
folder no longer includes dependencies such as Lit bundled into it. This is to ensure
users who have overlapping dependencies in their own project are able to only include one copy of them. This
however means that the library in /dist
will not work unbundled.
+ /cdn
If you wish to include fetch the library with all dependencies bundled into it, use the new
/cdn
folder. They contain the same exact set of components, styles, etc., but
/cdn
has the dependencies of Modes UI bundled into it. This is used in the new
modes-docs-template
for example, as the library is just imported with a simple module script in HTML.
If you are using "moduleResolution": "Bundler"
(included in TypeScript 5), or
"NodeNext"
/"Node16"
in your tsconfig.json
file, you may have to
modify your cherry picked imports as shown below.
Using "moduleResolution: "Node"
/** React wrapped component */ import MOButton from '@metsooutotec/modes-web-components/dist/react/button'; /** Just the web component */ import '@metsooutotec/modes-web-components/dist/components/button/button';
Using "moduleResolution: "Bundler"
/** React wrapped component */ import MOButton from '@metsooutotec/modes-web-components/dist/react/button/index.js'; /** Just the web component */ import '@metsooutotec/modes-web-components/dist/components/button/button.js';
Events
Version 4.0 exports Typescript types for all Modes UI events. Event types can be imported either
individually as a default export from each event type file, or in bulk from the events.ts
file
which re-exports all events in a single file.
This removes the need for consumers using TypeScript to redefine the events, and ensures that any breaking changes in Modes UI events will be detected at transpile time by consumers.
Emitted events inside components are now more consistent, before some mo-change
events had a
detail, and the detail had mixed contents. Now all mo-change
events have no
detail
object attached to them, and some use cases have been refactored in to new events. See
all the component specific event changes below.
-
Chip no longer emits
mo-select
when selected, now emitsmo-change
-
File dropzone no longer emits a
detail
alongsidemo-abort
,mo-remove
, andmo-change
events-
some are replaced by new
mo-transfer-abort
,mo-transfer-error
,mo-transfer-load
events that still contain the payload in the detail, see the new event files for details
-
some are replaced by new
-
Pagination now fires
mo-page-change
event instead ofmo-change
event, detail remains the same -
Rich text editor now emits a new
mo-editor-update
event when the Editor has finished updating (instead of a detailedmo-change
) -
The deprecated Toggle button no longer emits a detail alongside
the
mo-change
event -
Time picker now emits a new
mo-time-change
event instead of themo-change
event
You can also now import the event type for use in your callbacks, as shown below.
import { useCallback, useState } from 'react'; import { MOInput, MOInputEvent } from '@metsooutotec/modes-web-components/dist/react'; import type MOInputElement from '@metsooutotec/modes-web-components/dist/components/input/input'; function MyComponent() { const [value, setValue] = useState(''); const onInput = useCallback((event: MoInputEvent) => { setValue(event.detail); }, []); return <MOInput value={value} onMoInput={event => setValue((event.target as MOInputElement).value)} />; } export default MyComponent;
Note that events are strictly camel cased, as opposed to the components. The input event for example can
be listened to using onMoInput
and the name of the event type is MoInputEvent
,
not onMOInput
and MOInputEvent
.
Combobox
The mo-combobox
received a major refactoring in the way it handles its internal value
(#192). Below is a list of the changes made
-
The
mo-combobox
now aligns withmo-select
by using a space-delimited string as an attribute and an array of strings as a property when multiple selections are enabled. -
The initial and programmatic selection for
mo-combobox
is now managed solely through thevalue
property. -
The
hierarchical-content
attribute has been removed frommo-combobox
as it is no longer necessary. -
The
visibleValue
is no longer a public property onmo-combobox
; instead, use thevalue
property to control the selection. -
The
mo-tree-item
component (used internally within combobox) no longer emitsmo-select
andmo-deselect
events. Themo-selection-change
event frommo-tree
is now the only event emitted from the tree. -
The
mo-combobox
no longer automatically closes on value selection, use the newclose-on-selection
attribute to control this behavior
-
The
mo-combobox
now matches results that start with the query, rather than just checking if they include the query as a substring. -
The
mo-combobox
now highlights the non-queried part of the results instead of the query itself. -
A public
reset()
function has been added tomo-combobox
that properly resets the combobox to an empty state. -
Internal functions of
mo-combobox
are now properly marked as private, with JSDoc added to public functions. -
A virtualization regression bug in
mo-combobox
has been fixed, ensuring that new items load correctly when filtering the tree items. -
The simple
mo-combobox
now correctly sets an initial selection based on thevalue
attribute matching one of the items in theoptions
object. -
Programmatically changing the
value
attribute now selects the nodes with the matching IDs, and in cases of multiple selections, values are separated by spaces, similar tomo-select
.
selected nodes:
<div> <mo-combobox style="flex: 1 1 auto;" label="Celestial objects" placeholder="Select variable" id="tree-controlled" help-text="Choose one option" clearable ></mo-combobox> <mo-input help-text="Input node id to be selected here." id="node-id-input" placeholder="Example: 2-3-1-2" label="value" ></mo-input> </div> <mo-divider></mo-divider> <div> <mo-combobox style="flex: 1 1 auto;" label="Celestial objects" placeholder="Select variable" id="tree-controlled-multiple" help-text="Multiple options can be selected" value="2-1-1 1-1-2" selection-mode="multiple" clearable ></mo-combobox> <mo-input id="node-id-input-multiple" value="2-1-1 1-1-2" help-text="Input node id to be selected here." placeholder="Example: 2-1-1 1-1-2" label="value" ></mo-input> </div> <pre id="pre">selected nodes:</pre> <script> const treeBox = document.querySelector('#tree-controlled'); const treeBoxMulti = document.querySelector('#tree-controlled-multiple'); const input = document.querySelector('#node-id-input'); const inputMulti = document.querySelector('#node-id-input-multiple'); const pre = document.querySelector('#pre'); const treeItems = [ { name: 'Galaxies', id: '1', children: [ { name: 'Elliptical', id: '1-1', children: [ { name: 'IC 1101', id: '1-1-1' }, { name: 'Hercules A', id: '1-1-2', selected: true }, { name: 'A2261-BCG', id: '1-1-3' }, { name: 'ESO 306-17', id: '1-1-4' }, { name: 'ESO 444-46', id: '1-1-5' } ] }, { name: 'Spiral', id: '1-2', children: [ { name: "Rubin's Galaxy", id: '1-2-1' }, { name: 'Comet Galaxy', id: '1-2-2' }, { name: 'Condor Galaxy', id: '1-2-3' }, { name: 'Tadpole Galaxy', id: '1-2-4' }, { name: 'Andromeda Galaxy', id: '1-2-5' } ] } ] }, { name: 'Planets', id: '2', children: [ { name: 'Sub-Earth', id: '2-1', children: [ { name: 'Mars', id: '2-1-1' }, { name: 'Mercury', id: '2-1-2' } ] }, { name: 'Giant', id: '2-2', children: [ { name: 'Jupiter', id: '2-2-1' }, { name: 'Saturn', id: '2-2-2' }, { name: 'Uranus', id: '2-2-3' }, { name: 'Neptune', id: '2-2-4' } ] }, { name: 'Exoplanet', id: '2-3', children: [ { name: 'Potentially habitable', id: '2-3-1', children: [ { name: 'Alpha Centauri', id: '2-3-1-1' }, { name: 'Ross 128', id: '2-3-1-2' }, { name: 'Wolf 1061', id: '2-3-1-3' } ] }, { name: 'Epsilon Eridani', id: '2-3-2' }, { name: 'YZ Ceti', id: '2-3-4' } ] } ] } ]; treeBox.treeItems = treeItems; treeBoxMulti.treeItems = treeItems; treeBoxMulti.addEventListener('mo-selection-change', e => { pre.textContent = 'selected nodes: ' + treeBoxMulti.value; }); input.addEventListener('mo-input', () => { treeBox.value = input.value; }); inputMulti.addEventListener('mo-input', () => { treeBoxMulti.value = inputMulti.value; }); </script>
import { MOCombobox, MODivider, MOInput, MOButton } from '@metsooutotec/modes-web-components/dist/react'; const treeItems = [ { name: 'Galaxies', id: '1', children: [ { name: 'Elliptical', id: '1-1', children: [ { name: 'IC 1101', id: '1-1-1' }, { name: 'Hercules A', id: '1-1-2' }, { name: 'A2261-BCG', id: '1-1-3' }, { name: 'ESO 306-17', id: '1-1-4' }, { name: 'ESO 444-46', id: '1-1-5' } ] }, { name: 'Spiral', id: '1-2', children: [ { name: "Rubin's Galaxy", id: '1-2-1' }, { name: 'Comet Galaxy', id: '1-2-2' }, { name: 'Condor Galaxy', id: '1-2-3' }, { name: 'Tadpole Galaxy', id: '1-2-4' }, { name: 'Andromeda Galaxy', id: '1-2-5' } ] } ] }, { name: 'Planets', id: '2', children: [ { name: 'Sub-Earth', id: '2-1', children: [ { name: 'Mars', id: '2-1-1' }, { name: 'Mercury', id: '2-1-2' } ] }, { name: 'Giant', id: '2-2', children: [ { name: 'Jupiter', id: '2-2-1' }, { name: 'Saturn', id: '2-2-2' }, { name: 'Uranus', id: '2-2-3' }, { name: 'Neptune', id: '2-2-4' } ] }, { name: 'Exoplanet', id: '2-3', children: [ { name: 'Potentially habitable', id: '2-3-1', children: [ { name: 'Alpha Centauri', id: '2-3-1-1' }, { name: 'Ross 128', id: '2-3-1-2' }, { name: 'Wolf 1061', id: '2-3-1-3' } ] }, { name: 'Epsilon Eridani', id: '2-3-2' }, { name: 'YZ Ceti', id: '2-3-4' } ] } ] } ]; const inputRef = useRef(null); const cbRef = useRef(null); const setNode = () => { cbRef.current.selectNode(cbRef.current.getNodeById(inputRef.value)); }; const App = () => ( <> <MOCombobox label="Celestial objects" placeholder="Select variable" clearable ref={cbRef} treeItems={treeItems} ></MOCombobox> <MODivider></MODivider> <MOInput ref={inputRef} placeholder="Example: 2-3-1-2" label="Node id"></MOInput> <br /> <MOButton onClick={setNode}>Select node</MOButton> </> );
Before 4.0, the initial selection could be achieved by setting selected: true
on the nodes
themselves in the treeItems
object. This is deprecated in >4.0 and the behavior should be
implemented as described above.
LightningChart components
Data visualization components based on LightningCharts have been removed from Modes UI and they are now their own standalone library in Modes LC components. Anyone with a Metso GitHub license will be able to access the new library, but the project must ensure they have proper licensing before beginning usage. The new library has a demo site hosted in a separate static web application:
The usage of these components requires a license, so consult with the design system or Geminex team before starting work on implementing them in to your project.
Tree
The mo-tree-item
component (used internally within combobox) no longer emits
mo-select
and mo-deselect
events. The mo-selection-change
event from
mo-tree
is now the only event emitted from the tree.
Font changes
Previously Modes UI had two separate font-families and tokens defined for the Light
and
Regular
variants of GT-Eesti
. Since 4.0, these two variants will be combined in to
just one font: var(--mo-font-sans)
will now contain both of these and the
Regular
(Heading style) can be accessed by setting the font-weight
to either
bold
or 700
.
All components will use the new syntax starting from 4.0, but if you previously used
font-family: GT-Eesti-Light
or var(--mo-font-sans-regular)
in your project for
your custom styling, you will have to modify these to fit the new logic:
/** Deprecated way (<= 3.2.0) */ .header { /** Deprecated token */ font-family: var(--mo-font-sans-regular); /** No longer included in the library with this name */ font-family: 'GT-Eesti-Light'; } /** New way (>= v4.0) */ .header { /** Contains both GT-Eesti-Light and GT-Eesti-Regular */ font-family: var(--mo-font-sans); /** The only font-family included in the library since 4.0 */ font-family: 'GT-Eesti' /** Set font-weight to 700 to use GT-Eesti-Regular */ font-weight: var(--mo-font-weight-bold); }
See the typography section for more information.
New components
Version 4.0 brings a couple simple components that open up new ways to use the mo-menu
within
your application.
Context menu
The new mo-context-menu
components allows you to
customize the browser
default context menu
when the user clicks within a trigger area that you have specified.
<mo-context-menu closeOnSelection> <div class="trigger-area" slot="trigger"> Right click in this area to trigger a context menu </div> <mo-menu density="compact"> <mo-menu-item>Copy</mo-menu-item> <mo-menu-item>Paste</mo-menu-item> <mo-menu-item>Cut</mo-menu-item> <mo-menu-item disabled value="disabled">Disabled</mo-menu-item> <mo-divider></mo-divider> <mo-menu-item type="checkbox" checked value="copy">Show toolbar</mo-menu-item> <mo-divider></mo-divider> <mo-menu-item> Find <mo-icon slot="prefix" name="search"></mo-icon> <mo-menu density="compact" slot="submenu"> <mo-menu-item value="find">Find…</mo-menu-item> <mo-menu-item value="find-previous">Find previous</mo-menu-item> <mo-menu-item value="find-next">Find next</mo-menu-item> </mo-menu> </mo-menu-item> <mo-menu-item> Transformations <mo-icon slot="prefix" name="text-style"></mo-icon> <mo-menu density="compact" slot="submenu"> <mo-menu-item value="uppercase">Make uppercase</mo-menu-item> <mo-menu-item value="lowercase">Make lowercase</mo-menu-item> <mo-menu-item value="capitalize">Capitalize</mo-menu-item> </mo-menu> </mo-menu-item> </mo-menu> </mo-context-menu> <style> .trigger-area { border: 1px dashed var(--mo-color-neutral-70); padding: 4rem 2rem; text-align: center; } </style>
import { MOMenu, MOMenuItem, MODivider, MOIcon, MOContextMenu } from '@metsooutotec/modes-web-components/dist/react'; const App = () => ( <MOContextMenu closeOnSelection> <div className="trigger-area" slot="trigger"> Right click in this area to trigger a context menu </div> <MOMenu density="compact"> <MOMenuItem>Copy</MOMenuItem> <MOMenuItem>Paste</MOMenuItem> <MOMenuItem>Cut</MOMenuItem> <MOMenuItem disabled value="disabled">Disabled</MOMenuItem> <MODivider></MODivider> <MOMenuItem type="checkbox" checked value="copy">Show toolbar</MOMenuItem> <MODivider></MODivider> <MOMenuItem> Find <MOIcon slot="prefix" name="search"></MOIcon> <MOMenu density="compact" slot="submenu"> <MOMenuItem value="find">Find…</MOMenuItem> <MOMenuItem value="find-previous">Find previous</MOMenuItem> <MOMenuItem value="find-next">Find next</MOMenuItem> </MOMenu> </MOMenuItem> <MOMenuItem> Transformations <MOIcon slot="prefix" name="text-style"></MOIcon> <MOMenu density="compact" slot="submenu"> <MOMenuItem value="uppercase">Make uppercase</MOMenuItem> <MOMenuItem value="lowercase">Make lowercase</MOMenuItem> <MOMenuItem value="capitalize">Capitalize</MOMenuItem> </MOMenu> </MOMenuItem> </MOMenu> </MOContextMenu> )
Kbd
The mo-kbd
is a simple utility that allows you to visualize keyboard shortcuts for your users
easily and consistently. These shortcuts are typically shown within menus or inputs.
<mo-menu class="kbd-menu" style="max-width: 200px;"> <mo-menu-item> Copy <mo-kbd size="small" slot="suffix" keys="ctrl">C</mo-kbd> </mo-menu-item> <mo-menu-item> Paste <mo-kbd size="small" slot="suffix" keys="ctrl">V</mo-kbd> </mo-menu-item> <mo-menu-item> Cut <mo-kbd size="small" slot="suffix" keys="ctrl">X</mo-kbd> </mo-menu-item> </mo-menu> <style> .kbd-menu mo-menu-item::part(checked-icon) { width: 0.25em; } .kbd-menu mo-menu-item::part(submenu-icon) { width: 0em; } </style>
import { MOKbd, MOMenu, MOMenuItem } from '@metsooutotec/modes-web-components/dist/react'; const App = () => ( <MOMenu class="kbd-menu" style="max-width: 200px;"> <MOMenuItem> Copy <MOKbd size="small" slot="suffix" keys="ctrl">C</MOKbd> </MOMenuItem> <MOMenuItem> Paste <MOKbd size="small" slot="suffix" keys="ctrl">V</MOKbd> </MOMenuItem> <MOMenuItem> Cut <MOKbd size="small" slot="suffix" keys="ctrl">X</MOKbd> </MOMenuItem> </MOMenu> );
New features
Version 4.0 also includes some non-breaking feature updates to some existing components.
Zooming and panning in data visualization
The mo-line-chart
and mo-scatter-plot
zooming functionality has been improved by
default and it’s now more customizable. Highlighting an area to zoom is now the default and panning can be
enabled by holding down the zoomOptions
attribute.
<mo-scatter-plot yAxisLabel="Vertical displacement" xAxisLabel="Horizontal displacement" yAxisUnit="mm" xAxisUnit="mm" title="Scatter plot" subtitle="With zoom & pan" zoomable id="zooming" ></mo-scatter-plot> <mo-button id="reset-zoom-btn">Reset zoom</mo-button> <script> const chart = document.querySelector('#zooming'); const resetBtn = document.querySelector('#reset-zoom-btn'); const dataOne = [ { x: 1.44, y: 9.522 }, { x: 8.953, y: 5.912 }, { x: 0.533221, y: 5.53 }, { x: 3.5, y: 7.35 }, { x: 6.47, y: 4.98 }, { x: 7.723, y: 5.91 }, { x: 5.821123, y: 6.83 } ]; const dataTwo = [ { x: 1.12, y: 5.7 }, { x: 7.5222, y: 4.91 }, { x: 1.55, y: 4.4 }, { x: 4.12, y: 6.911 }, { x: 6.15, y: 4.424 }, { x: 7.1, y: 5.421 }, { x: 6, y: 7.152 } ]; const dataThree = [ { x: 3.123, y: 3.17 }, { x: 2.422, y: 4.11453 }, { x: 1.1, y: 2 }, { x: 2.8235, y: 2.512 }, { x: 9.2, y: 7.411 }, { x: 8.3, y: 7.453 }, { x: 4.432, y: 3.811 } ]; const datasets = [ { label: 'Dataset #1', data: dataOne }, { label: 'Dataset #2', data: dataTwo }, { label: 'Dataset #3', data: dataThree } ]; resetBtn.addEventListener('click', () => { chart.resetZoom(); }); chart.datasets = datasets; </script>
import { MOScatterPlot } from '@metsooutotec/modes-web-components/dist/react'; import { useRef } from 'react'; const chartRef = useRef(null); const dataOne = [ { x: 1.44, y: 9.522 }, { x: 8.953, y: 5.912 }, { x: 0.533221, y: 5.53 }, { x: 3.5, y: 7.35 }, { x: 6.47, y: 4.98 }, { x: 7.723, y: 5.91 }, { x: 5.821123, y: 6.83 } ]; const dataTwo = [ { x: 1.12, y: 5.7 }, { x: 7.5222, y: 4.91 }, { x: 1.55, y: 4.4 }, { x: 4.12, y: 6.911 }, { x: 6.15, y: 4.424 }, { x: 7.1, y: 5.421 }, { x: 6, y: 7.152 } ]; const dataThree = [ { x: 3.123, y: 3.17 }, { x: 2.422, y: 4.11453 }, { x: 1.1, y: 2 }, { x: 2.8235, y: 2.512 }, { x: 9.2, y: 7.411 }, { x: 8.3, y: 7.453 }, { x: 4.432, y: 3.811 } ]; const datasets = [ { label: 'Dataset #1', data: dataOne }, { label: 'Dataset #2', data: dataTwo }, { label: 'Dataset #3', data: dataThree } ]; const resetZoom = () => { chartRef.resetZoom(); }; const App = () => ( <> <MOScatterPlot ref={chartRef} yAxisLabel="Vertical displacement" xAxisLabel="Horizontal displacement" yAxisUnit="mm" xAxisUnit="mm" zoomable datasets={datasets} title="Scatter plot" subtitle="With point styling" ></MOScatterPlot> <MOButton onClick={resetZoom}></MOButton> </> );
By default mo-line-chart
and mo-scatter-plot
now reset their zoom when double
clicked. Find all the zooming options in the
plugin’s documentation.
Programmatic values in date picker
The value
attribute can now be properly set to either a valid
datetime string
(that follows the given format
), or a Date object
to
set the current value programmatically. This will also work for ranges, and it will update the input and the
calendars.
<mo-date-picker id="programmatic" closeOnSelection format="dd.MM.yyyy" label="Date today" help-text="The value has been set to today using new Date()" ></mo-date-picker> <br /> <mo-date-picker format="dd.MM.yyyy" value="18.04.2024 - 03.07.2024" selection="range" help-text="The range has been set using a string '18.04.2024 - 03.07.2024'" label="String range"></mo-date-picker> <script> const picker = document.querySelector('#programmatic'); picker.value = new Date(); </script>
import { MODatePicker } from '@metsooutotec/modes-web-components/dist/react'; const App = () => <> <MODatePicker closeOnSelection format="dd.MM.yyyy" label="Choose date" value={new Date()} helpText="Click the calendar icon to open the selector popup." ></MODatePicker> <br /> <MODatePicker format="dd.MM.yyyy" selection="range" value="18.04.2024 - 03.07.2024" label="Choose date"></MODatePicker> <>;
Documentation updates
Previously Modes UI used Docsify to generate the static documentation from raw markdown files. Since 4.0 Modes UI uses Eleventy instead to accomplish the same functionality. The advantage of using Eleventy is easier configuration and future development.
Templates
As Eleventy allows for easier customization, Modes UI now includes a way to display fullscreen templates. As the initial demo for this functionality, there is a sign-in template that shows a simple way to have a branding-aligned sign in screen for users.
See the full template page for the source code and more features
New documentation template
The new
modes-docs-template
GitHub repository contains a template for a static documentation site for Metso’s digital products. You can
clone or fork the repository and customize it to fit your products needs. Its
demo
is hosted as a Azure Static Web App and the project contains GitHub workflows that allow this to be
accomplished easily.
The template contains a lot of useful helper functions and has consistent styling with Modes UI by default. It also uses Eleventy for documentation generation, and as such documentation must be written as markdown. It also utilizes Modes UI components internally.
Other minor changes
-
introduced unit tests for
mo-menu
andmo-menu-item
, improved accessibility -
mo-table-head-cell
sorting icon now properly takes no extra space when it is visually hidden, added example to documentation -
a11y:
mo-table
now properly announces thearia-rowcount
for virtualized tables andaria-selected
for selected rows mo-table
borders are now drawn for each cell, rather than for each row-
added additional checks to
mo-donut-chart
to ensure updateColors doesn’t run before the component has finished updating - radial gauge now properly draws indicator when the
decimals
attribute is defined - removed flag illustrations
v3.0 (April 18, 2024)
Version 3.0 of Modes UI brings a lot of new functionality and components, and some breaking changes. See the details here to ensure migration to the latest major version is as smooth as possible.
Breaking changes
This release includes breaking changes to multiple components and to some more general aspects of the library.
Dependencies
Some dependencies were removed, some were upgraded. These changes likely won’t introduce breaking changes, but here is a list of the dependency changes.
- upgraded to Lit 3.0
-
upgraded to TypeScript 5
-
removed
ttypescript
andtypescript-transform-paths
dependencies as TypeScript 5 makes them obsolete
-
removed
-
added
@ctrl/tinycolor
dependency- removed
color
dependency
- removed
Date picker
The mo-date-picker
has been completely rewritten from
scratch. The dependency to duet-date-picker
has been removed, and it is now a completely custom
component. Internally it uses the new mo-calendar
to render
the date picker popup.
It should now cover all the features of the DateTimeRangeInput component from DSUI, while allowing for more customization and some additional features.
Notable features
- now includes proper keyboard interaction
- users can slot in arbitrary content to
footer
andsidebar
slots - full support for localization, with presets for
enUS, es, fi, pl, ptBR
- range selection from one input with two calendars
- manual input that is parsed using the customizable
format
attribute - built-in time input, with options to make it
readonly
and to give it a default value -
disabling certain dates from user selection using the
min
,max
, andisDateDisabled
attributes - mobile adaptiveness using the
mo-drawer
Footer and sidebar slots
Localization
<h5>Footer and sidebar slots</h5> <mo-date-picker id="footer-date" selection="range" label="Choose date range"> <div class="footer-div" slot="footer"> <mo-button id="cancel-btn" variant="secondary">Cancel</mo-button> <mo-button id="apply-btn">Apply</mo-button> </div> <div class="sidebar-div" slot="sidebar"> <mo-radio-group class="rg" hide-fieldset orientation="vertical"> <mo-radio-button value="86400">Day</mo-radio-button> <mo-radio-button value="604800">Week</mo-radio-button> <mo-radio-button value="2592000">Month</mo-radio-button> <mo-radio-button value="31536000">Year</mo-radio-button> </mo-radio-group> </div> <div class="footer-div" slot="mobile-footer"> <mo-button id="cancel-btn-mobile" variant="secondary">Cancel</mo-button> <mo-button id="apply-btn-mobile">Apply</mo-button> </div> <div class="sidebar-div" slot="mobile-sidebar"> <mo-radio-group class="rg-mobile" hide-fieldset> <mo-radio-button value="86400">Day</mo-radio-button> <mo-radio-button value="604800">Week</mo-radio-button> <mo-radio-button value="2592000">Month</mo-radio-button> <mo-radio-button value="31536000">Year</mo-radio-button> </mo-radio-group> </div> </mo-date-picker> <br /> <h5>Localization</h5> <div style="display: flex; flex-direction: column; gap: 8px;"> <mo-select style="align-self: end;" value="fi"> <mo-option value="fi">Suomi</mo-option> <mo-option value="en-US">English</mo-option> <mo-option value="pl">Polish</mo-option> <mo-option value="es">Español</mo-option> <mo-option value="pt-BR">Português</mo-option> </mo-select> <mo-date-picker id="localized" label="Valitse päivä" locale="fi" lang="fi" format="do MMMM, yyyy" closeOnSelection selection="range" ></mo-date-picker> </div> <script> const select = document.querySelector('mo-select'); const datePicker = document.querySelector('#localized'); const labels = { fi: 'Valitse päivä', 'en-US': 'Choose date', 'pt-BR': 'Escolha a data', es: 'Elegir fecha', pl: 'Wybierz datę' }; select.addEventListener('mo-change', e => { datePicker.locale = e.target.value; datePicker.lang = e.target.value.split('-')[0]; datePicker.label = labels[e.target.value]; }); const picker = document.querySelector('#footer-date'); const radioGrp = document.querySelector('.rg'); const radioGrpMobile = document.querySelector('.rg-mobile'); const cancelBtn = document.querySelector('#cancel-btn'); const applyBtn = document.querySelector('#apply-btn'); const cancelBtnMobile = document.querySelector('#cancel-btn'); const applyBtnMobile = document.querySelector('#apply-btn'); cancelBtn.addEventListener('click', () => { picker.reset(); picker.hide(); }); applyBtn.addEventListener('click', () => { picker.hide(); }); cancelBtnMobile.addEventListener('click', () => { picker.reset(); picker.hide(); }); applyBtnMobile.addEventListener('click', () => { picker.hide(); }); radioGrp.addEventListener('mo-change', e => { const value = e.target.value; const today = new Date(); const rangeEndMs = new Date().getTime() + parseInt(value) * 1000; const rangeEndDate = new Date(rangeEndMs); picker.value = [today, rangeEndDate]; }); radioGrpMobile.addEventListener('mo-change', e => { const value = e.target.value; const today = new Date(); const rangeEndMs = new Date().getTime() + parseInt(value) * 1000; const rangeEndDate = new Date(rangeEndMs); picker.value = [today, rangeEndDate]; }); </script> <style> .footer-div, .sidebar-div { display: flex; align-items: center; gap: var(--mo-spacing-small); width: 100%; justify-content: flex-end; box-sizing: border-box; } .sidebar-div { flex-direction: column; gap: 0; } mo-radio-button { margin: 0; } mo-date-picker::part(sidebar) { padding: 0; } mo-date-picker::part(mobile-sidebar) { padding: 0; } h5 { margin: 0; margin-bottom: 1rem; } @media screen and (max-width: 576px) { .footer-div, .sidebar-div { flex-direction: row; width: 100%; } .footer-div { justify-content: flex-end; } .sidebar-div { justify-content: flex-start; } } </style>
There are too many API changes to include a comprehensive list here. If you are using the old version of the component, you will pretty much have to implement it again from scratch following the new documentation.
Carousel
The mo-carousel
component has also been completely renewed,
as it was too opinionated an inflexible. The carousel slide has been renamed to
carousel item to align with the names of other components. It no
longer has pre-configured slots for content, and it does not apply a gradient overlay on the content. Users
must now fully dictate the content and its layout themselves, and gradient overlays must also be implemented
separately.
New carousel features
- multiple slides per view
- aspect ratio to customize the size of the carousel’s default viewport
- scroll hint (show a bit of the next slide)
- proper keyboard interaction
The carousel’s robust API makes it possible to extend and customize. This example syncs the active slide with a set of thumbnails, effectively creating a gallery-style carousel.
<mo-carousel class="carousel-thumbnails" navigation loop> <mo-carousel-item> <img alt="A snowy winter day at a quarry." src="/assets/examples/carousel/winter-quarry.webp" /> </mo-carousel-item> <mo-carousel-item> <img alt="Sun shines on the mining machine in a quarry." src="/assets/examples/carousel/sun-quarry.webp" /> </mo-carousel-item> <mo-carousel-item> <img alt="A busy city viewed from above." src="/assets/examples/carousel/busy-city.webp" /> </mo-carousel-item> <mo-carousel-item> <img alt="A large quarry for mining salt." src="/assets/examples/carousel/salt-quarry.webp" /> </mo-carousel-item> <mo-carousel-item> <img alt="A conveyor belt moving rocks in Lehigh Hanson." src="/assets/examples/carousel/lehigh-hanson.webp" /> </mo-carousel-item> </mo-carousel> <div class="thumbnails"> <div class="thumbnails__scroller"> <img alt="Thumbnail by 1" class="thumbnails__image active" src="/assets/examples/carousel/winter-quarry.webp" /> <img alt="Thumbnail by 2" class="thumbnails__image" src="/assets/examples/carousel/sun-quarry.webp" /> <img alt="Thumbnail by 3" class="thumbnails__image" src="/assets/examples/carousel/busy-city.webp" /> <img alt="Thumbnail by 4" class="thumbnails__image" src="/assets/examples/carousel/salt-quarry.webp" /> <img alt="Thumbnail by 5" class="thumbnails__image" src="/assets/examples/carousel/lehigh-hanson.webp" /> </div> </div> <style> .carousel-thumbnails { --slide-aspect-ratio: 3 / 2; } .thumbnails { display: flex; justify-content: center; } .thumbnails__scroller { display: flex; gap: var(--mo-spacing-small); overflow-x: auto; scrollbar-width: none; scroll-behavior: smooth; scroll-padding: var(--mo-spacing-small); } .thumbnails__scroller::-webkit-scrollbar { display: none; } .thumbnails__image { width: 64px; height: 64px; object-fit: cover; opacity: 0.3; will-change: opacity; transition: 250ms opacity; cursor: pointer; } .thumbnails__image.active { opacity: 1; } </style> <script> { const carousel = document.querySelector('.carousel-thumbnails'); const scroller = document.querySelector('.thumbnails__scroller'); const thumbnails = document.querySelectorAll('.thumbnails__image'); scroller.addEventListener('click', e => { const target = e.target; if (target.matches('.thumbnails__image')) { const index = [...thumbnails].indexOf(target); carousel.goToSlide(index); } }); carousel.addEventListener('mo-slide-change', e => { const slideIndex = e.detail.index; [...thumbnails].forEach((thumb, i) => { thumb.classList.toggle('active', i === slideIndex); if (i === slideIndex) { thumb.scrollIntoView({ block: 'nearest' }); } }); }); } </script>
import { useRef } from 'react'; import MOCarousel from '@metsooutotec/modes-web-components/dist/react/carousel'; import MOCarouselItem from '@metsooutotec/modes-web-components/dist/react/carousel-item'; import MODivider from '@metsooutotec/modes-web-components/dist/react/divider'; import MORange from '@metsooutotec/modes-web-components/dist/react/range'; const css = ` .carousel-thumbnails { --slide-aspect-ratio: 3 / 2; } .thumbnails { display: flex; justify-content: center; } .thumbnails__scroller { display: flex; gap: var(--mo-spacing-small); overflow-x: auto; scrollbar-width: none; scroll-behavior: smooth; scroll-padding: var(--mo-spacing-small); } .thumbnails__scroller::-webkit-scrollbar { display: none; } .thumbnails__image { width: 64px; height: 64px; object-fit: cover; opacity: 0.3; will-change: opacity; transition: 250ms opacity; cursor: pointer; } .thumbnails__image.active { opacity: 1; } `; const images = [ { src: '/assets/examples/carousel/winter-quarry.webp', alt: 'The sun shines on the mountains and trees (by Adam Kool on Unsplash' }, { src: '/assets/examples/carousel/sun-quarry.webp', alt: 'A waterfall in the middle of a forest (by Thomas Kelly on Unsplash' }, { src: '/assets/examples/carousel/busy-city.webp', alt: 'The sun is setting over a lavender field (by Leonard Cotte on Unsplash' }, { src: '/assets/examples/carousel/salt-quarry.webp', alt: 'A field of grass with the sun setting in the background (by Sapan Patel on Unsplash' }, { src: '/assets/examples/carousel/lehigh-hanson.webp', alt: 'A scenic view of a mountain with clouds rolling in (by V2osk on Unsplash' } ]; const App = () => { const carouselRef = useRef(); const thumbnailsRef = useRef(); const [currentSlide, setCurrentSlide] = useState(0); useEffect(() => { const thumbnails = Array.from(thumbnailsRef.current.querySelectorAll('.thumbnails__image')); thumbnails[currentSlide]..scrollIntoView({ block: 'nearest' }); }, [currentSlide]); const handleThumbnailClick = (index) => { carouselRef.current.goToSlide(index); } const handleSlideChange = (event) => { const slideIndex = e.detail.index; setCurrentSlide(slideIndex); } return ( <> <MOCarousel className="carousel-thumbnails" navigation loop onMOSlideChange={handleSlideChange}> {images.map({ src, alt }) => ( <MOCarouselItem> <img alt={alt} src={src} /> </MOCarouselItem> )} </MOCarousel> <div class="thumbnails"> <div class="thumbnails__scroller"> {images.map({ src, alt }, i) => ( <img alt={`Thumbnail by ${i + 1}`} className={`thumbnails__image ${i === currentSlide ? 'active' : ''}`} onClick={() => handleThumbnailClick(i)} src={src} /> )} </div> </div> <style>{css}</style> </> ); };
Card
- changed panel border color to
mo-color-secondary-70
-
mo-card
no longer has elevation (box-shadow) by default- manually apply the box-shadow (as shown below) if you wish to maintain the old style
This card has a footer. You can put all sorts of things in it!
This card has a footer. You can put all sorts of things in it!
<mo-card class="card-footer"> <div class="card-content"> <mo-badge variant="success" size="medium">New (3.0)</mo-badge> <br /> This card has a footer. You can put all sorts of things in it! </div> <div slot="footer"> <mo-rating></mo-rating> <mo-button slot="footer" variant="primary">Preview</mo-button> </div> </mo-card> <mo-divider></mo-divider> <mo-card class="card-footer old"> <div class="card-content"> <mo-badge size="medium">Old (< 3.0)</mo-badge> <br /> This card has a footer. You can put all sorts of things in it! </div> <div slot="footer"> <mo-rating></mo-rating> <mo-button slot="footer" variant="primary">Preview</mo-button> </div> </mo-card> <style> .card-footer { max-width: 300px; } .card-content { display: flex; flex-direction: column; gap: var(--mo-spacing-x-small); } .card-content mo-badge { align-self: flex-end; } .card-footer [slot='footer'] { display: flex; justify-content: space-between; align-items: center; } .old { box-shadow: var(--mo-elevation-1); --border-color: var(--mo-color-secondary-90); } </style>
Input changes
This version includes a lot of small changes in various input components to bring them all in line in terms of styling, sizing, and naming.
Select
- the new
mo-option
is now the child option element instead ofmo-menu-item
-
the
mo-select
event has been renamed tomo-input
, for consistency with other input component events mo-select
partcontrol
changed tocombobox
- renamed attribute
max-tags-visible
tomax-options-visible
The mo-select
no longer uses mo-menu-item
as its internal option element.
Replacing any references to mo-menu-item
inside a select with mo-option
should be
enough for most users to migrate:
<mo-select label="Select one"> <mo-menu-item value="option-1">Option 1</mo-menu-item> <mo-menu-item value="option-2">Option 2</mo-menu-item> <mo-menu-item value="option-3">Option 3</mo-menu-item> </mo-select>
<mo-select label="Select one"> <mo-option value="option-1">Option 1</mo-option> <mo-option value="option-2">Option 2</mo-option> <mo-option value="option-3">Option 3</mo-option> </mo-select>
<mo-select label="Select one"> <mo-option value="option-1">Option 1</mo-option> <mo-option value="option-2">Option 2</mo-option> <mo-option value="option-3">Option 3</mo-option> </mo-select>
Password toggling
-
renamed
toggle-password
attribute topassword-toggle
inmo-input
to match the newpassword-visible
attribute that allows for programmatic password visibility setting
<mo-input id="password-input" type="password" password-toggle></mo-input> <br /> <mo-button id="visibility-toggle-v">Show password</mo-button> <mo-button id="visibility-toggle-h">Hide password</mo-button> <script> const btnV = document.getElementById('visibility-toggle-v'); const btnH = document.getElementById('visibility-toggle-h'); const input = document.getElementById('password-input'); btnV.addEventListener('click', () => { input.passwordVisible = true; }); btnH.addEventListener('click', () => { input.passwordVisible = false; }); </script>
Error and success state
The mo-input
, mo-select
, mo-combobox
, mo-date-picker
,
and mo-time-picker
now include the error
, errorText
,
success
, successText
attributes that can be used to display error and success
states easily. This is mainly useful for projects that use a third party validation library such as
Zod to handle
input validation. See the input example for an
interactive example.
<mo-input label="Success" successText="Good input."></mo-input> <br /> <mo-input label="Error" errorText="Bad input."></mo-input>
Sizing
New
- added new
x-large
size tomo-button
mo-checkbox
now has asize
attribute-
default sizing is now consistent at 32px across multiple components such as
mo-input
,mo-select
,mo-date-picker
<div style="display: flex; flex-direction: column; gap: 16px;"> Old default size (was medium = 40px) <mo-input placeholder="Input here" size="large"></mo-input> <mo-select size="large" value="1"> <mo-option value="1">Option 1</mo-option> </mo-select> </div> <mo-divider></mo-divider> <div style="display: flex; flex-direction: column; gap: 16px;"> New default size (is medium = 32px) <mo-input placeholder="Input here"></mo-input> <mo-select value="1"> <mo-option value="1">Option 1</mo-option> </mo-select> </div>
All input components now follow the same sizing logic, so using them together in a form should look more
uniform. The size
attributes have been realigned so that e.g., the small
size for
different components always maps to the same pixel value. Below is a table of the new sizing tokens.
--mo-input-height-small
--mo-input-height-medium
--mo-input-height-large
--mo-input-height-x-large
--mo-input-height-2x-large
Not every single input has all of these options for sizing. For example the largest sizes are mainly used
for the mo-button
, and specifically in a usage context where the users wear gloves and use
touch screens. Stick to the default or one step smaller for most use cases.
Color picker
mo-color-picker
no longer supports css variables as thevalue
- removed default swatches from
mo-color-picker
- style is now rectangular instead of circular
<mo-color-picker value="#eb2814"></mo-color-picker>
New components
This version adds some low-level utility components such as the
mo-label
and
mo-popup
. The new mo-radio-button
replaces the
mo-toggle-button
, and the new mo-option
replaces the
mo-menu-item
as the element to include as children of the
mo-select
.
Calendar
The mo-calendar
component shows a monthly view of the
Gregorian calendar, optionally allowing users to interact with dates. Used internally in the new
mo-date-picker
.
<mo-calendar></mo-calendar>
Label
The mo-label
is a simple, atomic component that is used
internally within input components. It can be used to add a label for your custom input component that will
be consistent with input component styling, and will have functionality such as the
required
attribute.
<mo-label required>Custom label</mo-label>
Option
The mo-option
replaces the
mo-menu-item
as the element to include as children of the
mo-select
. Options define the selectable items within various form controls such as select.
<mo-select label="Select one"> <mo-option value="option-1">Option 1</mo-option> <mo-option value="option-2">Option 2</mo-option> <mo-option value="option-3">Option 3</mo-option> </mo-select>
Radio Button
The mo-radio-button
is designed to be used with
radio groups. When a radio button has focus, the arrow keys can be
used to change the selected option just like standard radio controls. The
mo-radio-button
replaces the
mo-toggle-button
component, which has now been marked
as deprecated.
<mo-radio-group label="Select an option" name="a" value="1"> <mo-radio-button value="1">Option 1</mo-radio-button> <mo-radio-button value="2">Option 2</mo-radio-button> <mo-radio-button value="3">Option 3</mo-radio-button> </mo-radio-group>
Popup
mo-popup
is a low-level utility built specifically for
positioning elements. Do not mistake it for a tooltip or similar because
it does not facilitate an accessible experience! Almost every correct usage of
<mo-popup>
will involve building other components. It should rarely, if ever, occur
directly in your HTML.
You can use it as a building block for your own custom components where you need to create a popup that is
relative to a parent element. The popup is used internally in components such as
mo-dropdown
and mo-select
.
<div class="popup-overview"> <mo-popup placement="top" active> <span slot="anchor"></span> <div class="box"></div> </mo-popup> <div class="popup-overview-options"> <mo-select label="Placement" name="placement" value="top" class="popup-overview-select"> <mo-option value="top">top</mo-option> <mo-option value="top-start">top-start</mo-option> <mo-option value="top-end">top-end</mo-option> <mo-option value="bottom">bottom</mo-option> <mo-option value="bottom-start">bottom-start</mo-option> <mo-option value="bottom-end">bottom-end</mo-option> <mo-option value="right">right</mo-option> <mo-option value="right-start">right-start</mo-option> <mo-option value="right-end">right-end</mo-option> <mo-option value="left">left</mo-option> <mo-option value="left-start">left-start</mo-option> <mo-option value="left-end">left-end</mo-option> </mo-select> <mo-input type="number" name="distance" label="distance" value="0"></mo-input> <mo-input type="number" name="skidding" label="Skidding" value="0"></mo-input> </div> <div class="popup-overview-options"> <mo-switch name="active" checked>Active</mo-switch> <mo-switch name="arrow">Arrow</mo-switch> </div> </div> <script> const container = document.querySelector('.popup-overview'); const popup = container.querySelector('mo-popup'); const select = container.querySelector('mo-select[name="placement"]'); const distance = container.querySelector('mo-input[name="distance"]'); const skidding = container.querySelector('mo-input[name="skidding"]'); const active = container.querySelector('mo-switch[name="active"]'); const arrow = container.querySelector('mo-switch[name="arrow"]'); select.addEventListener('mo-change', () => (popup.placement = select.value)); distance.addEventListener('mo-input', () => (popup.distance = distance.value)); skidding.addEventListener('mo-input', () => (popup.skidding = skidding.value)); active.addEventListener('mo-change', () => (popup.active = active.checked)); arrow.addEventListener('mo-change', () => (popup.arrow = arrow.checked)); </script> <style> .popup-overview mo-popup { --arrow-color: var(--mo-color-primary-7); } .popup-overview span[slot='anchor'] { display: inline-block; width: 150px; height: 150px; border: dashed 2px var(--mo-color-secondary-70); margin: 50px; } .popup-overview .box { width: 100px; height: 50px; background: var(--mo-color-primary-7); border-radius: var(--mo-border-radius-medium); } .popup-overview-options { display: flex; flex-wrap: wrap; align-items: end; gap: 1rem; } .popup-overview-options mo-select { width: 160px; } .popup-overview-options mo-input { width: 100px; } .popup-overview-options + .popup-overview-options { margin-top: 1rem; } </style>
Other minor changes
Theming
-
the
info color has been updated to be more aligned with the brand -
added new
gray
theme, try it out using the theme selector on the top right, see the theming documentation for more information -
added new neutral color palette to the
light.css
anddark.css
theme files - added new accessibility documentation, with a tool to check accessibility between two Modes colors
Tab
- tabs are no longer all UPPERCASE, to align with branding writing guidelines
- keyboard interaction fixed
- styling updated to be consistent across different tab group positions, non-active tabs are no longer bold in text
<mo-tab-group> <mo-tab slot="nav" panel="general">General</mo-tab> <mo-tab slot="nav" panel="custom">Custom</mo-tab> <mo-tab slot="nav" panel="advanced">Advanced</mo-tab> <mo-tab slot="nav" panel="disabled" disabled>Disabled</mo-tab> <mo-tab-panel name="general">This is the general tab panel.</mo-tab-panel> <mo-tab-panel name="custom">This is the custom tab panel.</mo-tab-panel> <mo-tab-panel name="advanced">This is the advanced tab panel.</mo-tab-panel> <mo-tab-panel name="disabled">This is a disabled tab panel.</mo-tab-panel> </mo-tab-group>
Progress
-
mo-progress-ring
andmo-progress-bar
track color changed fromsecondary-70
tosecondary-90
, added outline aroundmo-progress-bar
. -
mo-progress-bar
has newappearance
attribute, which can be used to rendersuccess
oralert
progress bar to visually show the status of the progress. - changed the default track width of progress ring to 8px
mo-progress-ring
indicator now has a squared tip to align with branding
<div style="display: flex; flex-direction: column; gap: 16px;"> <mo-progress-ring indeterminate value="40"></mo-progress-ring> <mo-progress-bar value="10"></mo-progress-bar> <mo-progress-bar value="33" appearance="success"></mo-progress-bar> <mo-progress-bar value="66" appearance="alert"></mo-progress-bar> </div>
Radio
- refactored
mo-radio
,mo-radio-group
-
mo-radio-group
now has anorientation
attribute to have vertical layouts for themo-radio-button
s -
mo-radio-group
s now have fieldset styling by default (subtle border + padding), but it can be hidden usinghide-fieldset
<mo-radio-group label="Select an option" name="a" value="1"> <mo-radio value="1">Option 1</mo-radio> <mo-radio value="2">Option 2</mo-radio> <mo-radio value="3">Option 3</mo-radio> </mo-radio-group> <br /> <mo-radio-group hide-fieldset name="a" value="1"> <mo-radio-button value="1">Option 1</mo-radio-button> <mo-radio-button value="2">Option 2</mo-radio-button> <mo-radio-button value="3">Option 3</mo-radio-button> </mo-radio-group> <br /> <mo-radio-group hide-fieldset orientation="vertical" name="a" value="1"> <mo-radio-button value="1">Option 1</mo-radio-button> <mo-radio-button value="2">Option 2</mo-radio-button> <mo-radio-button value="3">Option 3</mo-radio-button> </mo-radio-group>
Time picker
- now has an
options
attribute that can be used to customize the time suggestions - now shows the invalid state inside the input, rather than beside the label
- has a new
no-suggestions
attribute to disable generation of suggestions - now has properly translated default error messages
<mo-time-picker help-text="Find available times in the dropdown, or input a manual time to submit a request for that time slot." min="12:00" max="18:00" id="custom" label="Appointment time" ></mo-time-picker> <script> const picker = document.getElementById('custom'); picker.options = [ { text: '15:35', value: '15:35' }, { text: '16:20', value: '16:20' }, { text: '16:45', value: '16:45' }, { text: '17:30', value: '17:30' } ]; </script>
Rating
mo-rating
now has proper keyboard controls, improved mouse detection logic- introduced new
mo-hover
event -
added
label
attribute to announce the rating to assistive devices for improved accessibility - fixed icon style, changed unselected color
<mo-rating id="demo-rating" value="3" label="Demo rating."></mo-rating> <script> const rating = document.querySelector('#demo-rating'); rating.addEventListener('mo-hover', e => { console.log(e.detail); }); </script>
Chip
- now has a new
outlined
variant
<mo-chip pill variant="info" outline> <mo-icon name="info" slot="suffix"></mo-icon> Info </mo-chip> <mo-chip pill variant="success" outline> <mo-icon name="ok-circle" slot="suffix"></mo-icon> Success </mo-chip> <mo-chip pill variant="warning" outline> <mo-icon name="exclamation-mark-circle" slot="suffix"></mo-icon> Warning </mo-chip> <mo-chip pill variant="alert" outline> <mo-icon name="alarm" slot="suffix"></mo-icon> Alert </mo-chip>
Internal changes
-
refactored all alias imports (
~/*
) to use relative imports (e.g.../..
) from component files (.ts
) -
removed all style imports from
.css
files, moved generic component styles to the main component file (.ts
) for performance reasons -
moved internal dependencies to static
dependencies
variable for each component for clarity - refactored every component to use relative imports instead of the import alias as it was causing issues with the test plugin
v2.0 (May 17, 2023)
Table
The mo-table
component has been completely rewritten. It now comes with
mo-table-row
, mo-table-cell
, mo-table-head
,
mo-table-body
and mo-table-head-cell
components that can be used to compose a
properly styled table, that comes with additional functionality such as row selection, sorting and
virtualization. Check out the new documentation for usage and code examples.
Carousel and carousel slide
The mo-carousel
and mo-carousel-slide
components have been refactored to be more
composable, instead of relying on attributes/properties for customization. This means they are more
flexible, customizable, and consistent with other Modes UI components.
Carousel
The pagination dots inside the mo-carousel
are no longer enabled by default. Use the
pagination
attribute to enable them.
Carousel slide
Previously the action button inside the carousel was defined using properties linkLabel
,
linkUrl
and linkTarget
. Now the button should be slotted in to the
button
slot on the mo-carousel-slide
. The attribute imageUrl
has been
removed, and the background image should now be supplied in the background
slot of the
mo-carousel-slide
.
The attribute introText
has been renamed to subtitle
.
<mo-carousel-slide introText="extra text" linkLabel="Button" linkUrl="https://metso.com/" linkTarget="_blank" imageUrl="https://images.pexels.com/photos/257700/pexels-photo-257700.jpeg" ></mo-carousel-slide>
<mo-carousel-slide subtitle="extra text"> <img slot="background" alt="industrial plant" src="https://images.pexels.com/photos/257700/pexels-photo-257700.jpeg" /> <mo-button href="https://metso.com" target="_blank" slot="button">Button</mo-button> </mo-carousel-slide>
Switch
The mo-switch
component no longer has the darkMode
attribute. Instead, creating a
dark mode variant of the mo-switch
should be accomplished using the prefix
and
suffix
slots. Alongside this change, the checked state no longer has the
ok
checkmark icon by default, and it must be added using the suffix
slot as shown
in the documentation.
<mo-switch> Switch </mo-switch> <mo-switch darkMode> Dark mode toggle </mo-switch>
<mo-switch> <mo-icon name="ok" slot="suffix"></mo-icon> Switch </mo-switch> <mo-switch> <mo-icon name="sun" slot="prefix"></mo-icon> <mo-icon name="moon-fill" slot="suffix"></mo-icon> Dark mode toggle </mo-switch>
Header
The mo-header
component is no longer contained
by default. This behaviour must be
enabled using the contained
attribute on the mo-header
. All of the code examples
on this site include this attribute to ensure the drawer opens within the example, not the context of the
entire page.