diff --git a/source/engine/viewer/domutils.js b/source/engine/viewer/domutils.js index 9dcb0f0..612b398 100644 --- a/source/engine/viewer/domutils.js +++ b/source/engine/viewer/domutils.js @@ -70,6 +70,13 @@ export function AddDiv (parentElement, className, innerHTML) return AddDomElement (parentElement, 'div', className, innerHTML); } +export function AddButtonElement (parentElement, className, innerHTML) +{ + let button = AddDomElement (parentElement, 'button', className, innerHTML); + button.setAttribute ('type', 'button'); + return button; +} + export function ClearDomElement (element) { while (element.firstChild) { @@ -139,3 +146,10 @@ export function CreateDiv (className, innerHTML) { return CreateDomElement ('div', className, innerHTML); } + +export function CreateButtonElement (className, innerHTML) +{ + let button = CreateDomElement ('button', className, innerHTML); + button.setAttribute ('type', 'button'); + return button; +} diff --git a/source/website/css/controls.css b/source/website/css/controls.css index 61893ae..9dd3c9d 100644 --- a/source/website/css/controls.css +++ b/source/website/css/controls.css @@ -51,18 +51,23 @@ div.ov_thin_scrollbar::-webkit-scrollbar-thumb background: #cccccc; } -div.ov_button +.ov_button { color: var(--ov_button_text_color); background: var(--ov_button_color); text-align: center; - padding: 3px; + padding: 8px 16px; + min-height: 44px; border: 1px solid var(--ov_button_color); border-radius: 5px; cursor: pointer; + display: inline-flex; + align-items: center; + justify-content: center; + box-sizing: border-box; } -div.ov_button.outline +.ov_button.outline { color: var(--ov_outline_button_text_color); background: transparent; @@ -173,7 +178,6 @@ input.ov_slider { height: 1px; background: var(--ov_border_color); - outline: none; -webkit-appearance: none; -moz-appearance: none; appearance: none; @@ -207,7 +211,7 @@ span.ov_slider_label bottom: -4px; } -div.ov_toggle +button.ov_toggle { width: 24px; height: 8px; @@ -218,7 +222,7 @@ div.ov_toggle cursor: pointer; } -div.ov_toggle_slider +button.ov_toggle div.ov_toggle_slider { width: 6px; height: 6px; @@ -227,12 +231,12 @@ div.ov_toggle_slider border: 1px solid var(--ov_foreground_color); } -div.ov_toggle.on +button.ov_toggle.on { background: var(--ov_foreground_color); } -div.ov_toggle.on div.ov_toggle_slider +button.ov_toggle.on div.ov_toggle_slider { background: var(--ov_background_color); transform: translateX(16px); @@ -247,13 +251,13 @@ div.ov_svg_icon.selected:hover color: var(--ov_hover_text_color); } -div.ov_button:hover +.ov_button:hover { background: var(--ov_button_hover_color); border: 1px solid var(--ov_button_hover_color); } -div.ov_button.outline:hover +.ov_button.outline:hover { background: var(--ov_outline_button_hover_color); border: 1px solid var(--ov_outline_button_color); diff --git a/source/website/css/core.css b/source/website/css/core.css index a33cd78..ba42b12 100644 --- a/source/website/css/core.css +++ b/source/website/css/core.css @@ -28,6 +28,21 @@ a text-decoration: none; } +button +{ + font-family: Quicksand, Helvetica, sans-serif; + font-size: 16px; +} + +button[class] +{ + color: inherit; + background: none; + border: 0px; + padding: 0px; + margin: 0px; +} + img { display: block; @@ -54,7 +69,18 @@ input, select, textarea { font-family: Quicksand, Helvetica, sans-serif; font-size: 16px; - outline: none; +} + +a:focus-visible, +button:focus-visible, +input:focus-visible, +select:focus-visible, +textarea:focus-visible, +canvas:focus-visible, +[role=button]:focus-visible +{ + outline: 2px solid var(--ov_focus_ring_color); + outline-offset: 2px; } @media (hover) diff --git a/source/website/css/dialogs.css b/source/website/css/dialogs.css index ff8f27f..e064723 100644 --- a/source/website/css/dialogs.css +++ b/source/website/css/dialogs.css @@ -57,7 +57,7 @@ div.ov_dialog div.ov_dialog_buttons_inner overflow: auto; } -div.ov_dialog div.ov_dialog_buttons div.ov_dialog_button +div.ov_dialog div.ov_dialog_buttons .ov_dialog_button { margin-left: 10px; width: 80px; @@ -104,7 +104,7 @@ div.ov_dialog div.ov_dialog_import_file_list overflow: auto; } -div.ov_dialog div.ov_dialog_file_link +div.ov_dialog .ov_dialog_file_link { color: var(--ov_button_color); padding: 5px; @@ -114,7 +114,7 @@ div.ov_dialog div.ov_dialog_file_link cursor: pointer; } -div.ov_dialog div.ov_dialog_file_link div.ov_file_link_img +div.ov_dialog .ov_dialog_file_link div.ov_file_link_img { color: var(--ov_button_color); margin-top: 2px; @@ -122,7 +122,7 @@ div.ov_dialog div.ov_dialog_file_link div.ov_file_link_img float: left; } -div.ov_dialog div.ov_dialog_file_link div.ov_dialog_file_link_text +div.ov_dialog .ov_dialog_file_link div.ov_dialog_file_link_text { float: left; } @@ -145,7 +145,7 @@ div.ov_dialog div.ov_dialog_copyable_input input box-sizing: border-box; } -div.ov_dialog div.ov_dialog_copyable_input div.ov_dialog_copyable_input_button +div.ov_dialog div.ov_dialog_copyable_input .ov_dialog_copyable_input_button { width: 28%; margin-left: 0px; @@ -196,12 +196,15 @@ div.ov_popup div.ov_popup_list overflow: auto; } -div.ov_popup div.ov_popup_list_item +div.ov_popup .ov_popup_list_item { padding: 10px; border-radius: 5px; cursor: pointer; overflow: auto; + display: block; + width: 100%; + text-align: left; } div.ov_popup div.ov_popup_list_item_icon @@ -299,18 +302,18 @@ div.ov_snapshot_dialog_separator @media (hover) { -div.ov_dialog div.ov_dialog_file_link:hover +div.ov_dialog .ov_dialog_file_link:hover { color: var(--ov_hover_text_color); background: var(--ov_hover_color); } -div.ov_dialog div.ov_dialog_file_link:hover div.ov_file_link_img +div.ov_dialog .ov_dialog_file_link:hover div.ov_file_link_img { color: var(--ov_hover_text_color); } -div.ov_popup div.ov_popup_list_item:hover +div.ov_popup .ov_popup_list_item:hover { background: var(--ov_hover_color); } diff --git a/source/website/css/navigator.css b/source/website/css/navigator.css index ae4f7b2..fd25606 100644 --- a/source/website/css/navigator.css +++ b/source/website/css/navigator.css @@ -6,14 +6,15 @@ div.ov_navigator_buttons overflow: auto; } -div.ov_navigator_button +.ov_navigator_button { float: left; cursor: pointer; - padding: 5px; + padding: 13px; + display: block; } -div.ov_navigator_button.right +.ov_navigator_button.right { float: right; } @@ -76,7 +77,7 @@ div.ov_navigator_info_panel div.ov_navigator_info_panel_title:hover background: var(--ov_hover_color); } -div.ov_navigator_button:hover +.ov_navigator_button:hover { background: var(--ov_hover_color); } diff --git a/source/website/css/panelset.css b/source/website/css/panelset.css index c4e2f08..d727495 100644 --- a/source/website/css/panelset.css +++ b/source/website/css/panelset.css @@ -8,10 +8,21 @@ div.ov_panel_set_right_container div.ov_panel_set_menu float: right; } -div.ov_panel_set_menu div.ov_panel_set_menu_button +.ov_panel_set_menu .ov_panel_set_menu_button { - padding: 10px; + padding: 13px; cursor: pointer; + display: block; +} + +.ov_panel_set_menu .ov_panel_set_menu_button.selected +{ + background: var(--ov_hover_color); +} + +.ov_panel_set_menu .ov_panel_set_menu_button.selected .ov_svg_icon +{ + color: var(--ov_selected_icon_color); } div.ov_panel_set_container div.ov_panel_set_content @@ -28,13 +39,16 @@ div.ov_panel_set_right_container div.ov_panel_set_content overflow: auto; } -div.ov_panel_button +.ov_panel_button { cursor: pointer; margin-top: 10px; border: 1px solid var(--ov_border_color); border-radius: 5px; overflow: auto; + display: block; + width: 100%; + text-align: left; } div.ov_panel_button_text @@ -60,13 +74,13 @@ div.ov_panel_button_left_icon @media (hover) { -div.ov_panel_button:hover +.ov_panel_button:hover { background: var(--ov_hover_color); } -div.ov_panel_set_menu div.ov_panel_set_menu_button:hover +.ov_panel_set_menu .ov_panel_set_menu_button:hover { background: var(--ov_hover_color); } diff --git a/source/website/css/sidebar.css b/source/website/css/sidebar.css index a849e74..bc0594c 100644 --- a/source/website/css/sidebar.css +++ b/source/website/css/sidebar.css @@ -40,18 +40,19 @@ div.ov_sidebar_parameter div.ov_sidebar_parameter_text float: left; } -div.ov_sidebar_image_picker +.ov_sidebar_image_picker { background-size: cover; background-position: center center; - width: 28px; - height: 13px; + width: 44px; + height: 26px; margin-top: 3px; margin-right: 10px; border: 1px solid var(--ov_border_color); border-radius: 3px; float: left; cursor: pointer; + display: block; } div.ov_sidebar_content @@ -105,7 +106,6 @@ div.ov_sidebar_content button.pcr-button margin: 3px 10px 3px 0px; border: 1px solid var(--ov_border_color); box-shadow: none; - outline: none; float: left; } diff --git a/source/website/css/themes.css b/source/website/css/themes.css index 8215978..e670b04 100644 --- a/source/website/css/themes.css +++ b/source/website/css/themes.css @@ -3,18 +3,18 @@ --ov_foreground_color: #000000; --ov_background_color: #ffffff; --ov_disabled_foreground_color: #cccccc; - --ov_button_color: #3393bd; - --ov_button_hover_color: #146a8f; + --ov_button_color: #1f6f94; + --ov_button_hover_color: #165876; --ov_button_text_color: #ffffff; - --ov_outline_button_color: #3393bd; - --ov_outline_button_hover_color: #c9e5f8; - --ov_outline_button_text_color: #3393bd; + --ov_outline_button_color: #1f6f94; + --ov_outline_button_hover_color: #d9e9f2; + --ov_outline_button_text_color: #1f6f94; --ov_icon_color: #263238; --ov_light_icon_color: #838383; - --ov_selected_icon_color: #3393bd; + --ov_selected_icon_color: #1f6f94; --ov_disabled_icon_color: #cccccc; - --ov_hover_color: #c9e5f8; - --ov_hover_text_color: #3393bd; + --ov_hover_color: #d9e9f2; + --ov_hover_text_color: #1f6f94; --ov_logo_text_color: #15334a; --ov_logo_border_color: #000000; --ov_toolbar_background_color: #f5f5f5; @@ -26,19 +26,20 @@ --ov_dialog_control_border_color: #e1e1e1; --ov_border_color: #dddddd; --ov_shadow: 0px 0px 10px #cccccc; + --ov_focus_ring_color: #0d5c80; --ov_foreground_color_dark: #fafafa; --ov_background_color_dark: #2a2b2e; --ov_disabled_foreground_color_dark: #888888; - --ov_button_color_dark: #3393bd; - --ov_button_hover_color_dark: #146a8f; + --ov_button_color_dark: #3e8fb5; + --ov_button_hover_color_dark: #5aa5c8; --ov_button_text_color_dark: #ffffff; - --ov_outline_button_color_dark: #c9e5f8; - --ov_outline_button_hover_color_dark: #2f6984; - --ov_outline_button_text_color_dark: #c9e5f8; + --ov_outline_button_color_dark: #9fd1ea; + --ov_outline_button_hover_color_dark: #315d70; + --ov_outline_button_text_color_dark: #d8f0fb; --ov_icon_color_dark: #fafafa; --ov_light_icon_color_dark: #bababa; - --ov_selected_icon_color_dark: #3393bd; + --ov_selected_icon_color_dark: #9fd1ea; --ov_disabled_icon_color_dark: #888888; --ov_hover_color_dark: #667c86; --ov_hover_text_color_dark: #fafafa; @@ -53,4 +54,5 @@ --ov_dialog_control_border_color_dark: #e1e1e1; --ov_border_color_dark: #444444; --ov_shadow_dark: 0px 0px 10px #222222; + --ov_focus_ring_color_dark: #8fc8e5; } diff --git a/source/website/css/treeview.css b/source/website/css/treeview.css index 77a3c54..f209940 100644 --- a/source/website/css/treeview.css +++ b/source/website/css/treeview.css @@ -27,20 +27,28 @@ div.ov_tree_view div.ov_tree_item_button_container float: right; } -div.ov_tree_view div.ov_tree_item_button +.ov_tree_view .ov_tree_item_button { color: var(--ov_light_icon_color); - padding: 5px; + padding: 13px; float: left; cursor: pointer; + display: block; } -div.ov_tree_view div.ov_tree_item_icon +.ov_tree_view div.ov_tree_item_icon, +.ov_tree_view button.ov_tree_item_icon { padding: 5px; float: left; } +.ov_tree_view button.ov_tree_item_toggle +{ + display: block; + cursor: pointer; +} + div.ov_tree_view div.ov_tree_item_name { padding: 4px 5px; diff --git a/source/website/css/website.css b/source/website/css/website.css index 62c214d..995474a 100644 --- a/source/website/css/website.css +++ b/source/website/css/website.css @@ -13,7 +13,7 @@ div.ov_color_circle div.header { overflow: auto; - display: none; + display: block; } div.title @@ -69,7 +69,7 @@ div.intro text-align: center; border: 2px dashed var(--ov_border_color); overflow: auto; - display: none; + display: block; } div.intro_content @@ -116,12 +116,16 @@ div.intro div.intro_file_formats a color: var(--ov_outline_button_text_color); text-decoration: none; font-size: 17px; - width: 50px; + min-width: 56px; + min-height: 44px; border-radius: 5px; padding: 4px 8px; margin: 6px 4px; border: 1px solid var(--ov_outline_button_color); - display: inline-block; + display: inline-flex; + align-items: center; + justify-content: center; + box-sizing: border-box; cursor: pointer; } @@ -212,19 +216,20 @@ div.ov_toolbar user-select: none; } -div.ov_toolbar div.ov_toolbar_button +.ov_toolbar .ov_toolbar_button { float: left; cursor: pointer; - padding: 10px; + padding: 13px; + display: block; } -div.ov_toolbar div.ov_toolbar_button.align_right +.ov_toolbar .ov_toolbar_button.align_right { float: right; } -div.ov_toolbar div.ov_toolbar_button.selected +.ov_toolbar .ov_toolbar_button.selected { background: var(--ov_toolbar_selected_color); } @@ -314,23 +319,27 @@ div.ov_bottom_floating_panel background: var(--ov_background_color); border-top: 1px solid var(--ov_border_color); width: 100%; - padding: 30px; + padding: 20px 24px; box-sizing: border-box; position: absolute; bottom: 0px; + display: flex; + flex-wrap: wrap; + align-items: center; + gap: 12px; } div.ov_bottom_floating_panel div.ov_floating_panel_text { - padding: 3px; - margin-bottom: 10px; - float: left; + padding: 3px 0px; + flex: 1 1 260px; } -div.ov_bottom_floating_panel div.ov_floating_panel_button +.ov_bottom_floating_panel .ov_floating_panel_button { width: 120px; - float: right; + flex: 0 0 auto; + margin-left: auto; } div.ov_measure_panel @@ -362,7 +371,7 @@ div.title_right div.header_button:hover color: var(--ov_button_color); } -div.ov_toolbar div.ov_toolbar_button:hover +.ov_toolbar .ov_toolbar_button:hover { background: var(--ov_hover_color); } @@ -392,6 +401,62 @@ div.intro_content width: auto; } +div.main_left_container.only_full_width, +div.main_right_container.only_full_width, +div.main_file_name.only_full_width +{ + display: block; +} + +div.main +{ + position: relative; +} + +div.main_file_name +{ + margin: 8px 56px; + font-size: 14px; +} + +div.main_left_container, +div.main_right_container +{ + position: absolute; + top: 0px; + bottom: 0px; + z-index: 2; + overflow: visible; +} + +div.main_left_container +{ + left: 0px; +} + +div.main_right_container +{ + right: 0px; +} + +div.main_navigator, +div.main_sidebar +{ + margin: 0px; + background: var(--ov_background_color); + box-shadow: var(--ov_shadow); +} + +div.main_splitter +{ + display: none; +} + +div.main_viewer +{ + float: none; +} + div.main_viewer canvas { border: 0px; @@ -410,7 +475,7 @@ div.ov_progress div.ov_bottom_floating_panel { - padding: 10px; + padding: 12px; } } diff --git a/source/website/dialog.js b/source/website/dialog.js index 64e411d..fb92605 100644 --- a/source/website/dialog.js +++ b/source/website/dialog.js @@ -1,5 +1,5 @@ -import { AddDiv, CreateDiv } from '../engine/viewer/domutils.js'; -import { AddSvgIconElement, CreateInlineColorCircle, IsHoverEnabled } from './utils.js'; +import { AddDiv, CreateDiv, AddButtonElement } from '../engine/viewer/domutils.js'; +import { AddSvgIconElement, CreateInlineColorCircle, IsHoverEnabled, SetElementAccessibleName } from './utils.js'; let currentDialog = null; @@ -164,10 +164,11 @@ export class ButtonDialog extends Dialog { function AddButton (button, buttonsDiv) { - let buttonDiv = AddDiv (buttonsDiv, 'ov_button ov_dialog_button', button.name); + let buttonDiv = AddButtonElement (buttonsDiv, 'ov_button ov_dialog_button', button.name); if (button.subClass) { buttonDiv.classList.add (button.subClass); } + SetElementAccessibleName (buttonDiv, button.name); buttonDiv.addEventListener ('click', () => { button.onClick (); }); @@ -221,7 +222,8 @@ export class ListPopup extends PopupDialog AddListItem (item, callbacks) { - let listItemDiv = AddDiv (this.listDiv, 'ov_popup_list_item'); + let listItemDiv = AddButtonElement (this.listDiv, 'ov_popup_list_item'); + SetElementAccessibleName (listItemDiv, item.name); if (item.icon) { AddSvgIconElement (listItemDiv, item.icon, 'left_inline'); } diff --git a/source/website/index.js b/source/website/index.js index 8b08974..b25530b 100644 --- a/source/website/index.js +++ b/source/website/index.js @@ -47,46 +47,42 @@ export function RegisterToolbarPlugin (plugin) export function StartWebsite () { - window.addEventListener ('load', () => { - if (window.self !== window.top) { - let noEmbeddingDiv = AddDiv (document.body, 'noembed'); - AddDiv (noEmbeddingDiv, null, Loc ('Embedding Online 3D Viewer in an iframe is not supported.')); - let link = AddDomElement (noEmbeddingDiv, 'a', null, Loc ('Open Online 3D Viewer')); - link.target = '_blank'; - link.href = window.self.location; - return; - } + if (window.self !== window.top) { + let noEmbeddingDiv = AddDiv (document.body, 'noembed'); + AddDiv (noEmbeddingDiv, null, Loc ('Embedding Online 3D Viewer in an iframe is not supported.')); + let link = AddDomElement (noEmbeddingDiv, 'a', null, Loc ('Open Online 3D Viewer')); + link.target = '_blank'; + link.href = window.self.location; + return; + } - document.getElementById ('intro_dragdrop_text').innerHTML = Loc ('Drag and drop 3D models here.'); - document.getElementById ('intro_formats_title').innerHTML = Loc ('Check an example file:'); + document.getElementById ('intro_dragdrop_text').textContent = Loc ('Drag and drop 3D models here.'); + document.getElementById ('intro_formats_title').textContent = Loc ('Check an example file:'); - let website = new Website ({ - headerDiv : document.getElementById ('header'), - headerButtonsDiv : document.getElementById ('header_buttons'), - toolbarDiv : document.getElementById ('toolbar'), - mainDiv : document.getElementById ('main'), - introDiv : document.getElementById ('intro'), - fileNameDiv : document.getElementById ('main_file_name'), - leftContainerDiv : document.getElementById ('main_left_container'), - navigatorDiv : document.getElementById ('main_navigator'), - navigatorSplitterDiv : document.getElementById ('main_navigator_splitter'), - rightContainerDiv : document.getElementById ('main_right_container'), - sidebarDiv : document.getElementById ('main_sidebar'), - sidebarSplitterDiv : document.getElementById ('main_sidebar_splitter'), - viewerDiv : document.getElementById ('main_viewer'), - fileInput : document.getElementById ('open_file') - }); - website.Load (); + let website = new Website ({ + headerDiv : document.getElementById ('header'), + headerButtonsDiv : document.getElementById ('header_buttons'), + toolbarDiv : document.getElementById ('toolbar'), + mainDiv : document.getElementById ('main'), + introDiv : document.getElementById ('intro'), + fileNameDiv : document.getElementById ('main_file_name'), + leftContainerDiv : document.getElementById ('main_left_container'), + navigatorDiv : document.getElementById ('main_navigator'), + navigatorSplitterDiv : document.getElementById ('main_navigator_splitter'), + rightContainerDiv : document.getElementById ('main_right_container'), + sidebarDiv : document.getElementById ('main_sidebar'), + sidebarSplitterDiv : document.getElementById ('main_sidebar_splitter'), + viewerDiv : document.getElementById ('main_viewer'), + fileInput : document.getElementById ('open_file') }); + website.Load (); } export function StartEmbed () { - window.addEventListener ('load', () => { - let embed = new Embed ({ - viewerDiv : document.getElementById ('embed_viewer'), - websiteLinkDiv : document.getElementById ('website_link') - }); - embed.Load (); + let embed = new Embed ({ + viewerDiv : document.getElementById ('embed_viewer'), + websiteLinkDiv : document.getElementById ('website_link') }); + embed.Load (); } diff --git a/source/website/navigatoritems.js b/source/website/navigatoritems.js index cfb5a89..d556c57 100644 --- a/source/website/navigatoritems.js +++ b/source/website/navigatoritems.js @@ -1,4 +1,5 @@ import { IsDefined } from '../engine/core/core.js'; +import { Loc } from '../engine/core/localization.js'; import { TreeViewButton, TreeViewButtonItem, TreeViewGroupButtonItem, TreeViewSingleItem } from './treeview.js'; export const NavigatorItemRecurse = @@ -29,13 +30,13 @@ export class MeshItem extends TreeViewButtonItem this.meshInstanceId = meshInstanceId; this.visible = true; - this.fitToWindowButton = new TreeViewButton ('fit'); + this.fitToWindowButton = new TreeViewButton ('fit', Loc ('Fit mesh to window')); this.fitToWindowButton.OnClick (() => { callbacks.onFitToWindow (this.meshInstanceId); }); this.AppendButton (this.fitToWindowButton); - this.showHideButton = new TreeViewButton ('visible'); + this.showHideButton = new TreeViewButton ('visible', Loc ('Show or hide mesh')); this.showHideButton.OnClick (() => { callbacks.onShowHide (this.meshInstanceId); }); @@ -85,13 +86,13 @@ export class NodeItem extends TreeViewGroupButtonItem this.callbacks = callbacks; this.visible = true; - this.fitToWindowButton = new TreeViewButton ('fit'); + this.fitToWindowButton = new TreeViewButton ('fit', Loc ('Fit node to window')); this.fitToWindowButton.OnClick (() => { this.callbacks.onFitToWindow (nodeId); }); this.AppendButton (this.fitToWindowButton); - this.showHideButton = new TreeViewButton ('visible'); + this.showHideButton = new TreeViewButton ('visible', Loc ('Show or hide node')); this.showHideButton.OnClick (() => { this.callbacks.onShowHide (nodeId); }); diff --git a/source/website/navigatorpanel.js b/source/website/navigatorpanel.js index 3dce6e5..c62a114 100644 --- a/source/website/navigatorpanel.js +++ b/source/website/navigatorpanel.js @@ -1,4 +1,4 @@ -import { AddDiv } from '../engine/viewer/domutils.js'; +import { AddDiv, AddButtonElement } from '../engine/viewer/domutils.js'; import { Panel } from './panelset.js'; import { TreeView } from './treeview.js'; import { AddSvgIconElement } from './utils.js'; @@ -11,7 +11,7 @@ export class NavigatorPopupButton this.callbacks = null; this.popup = null; - this.button = AddDiv (this.parentDiv, 'ov_panel_button'); + this.button = AddButtonElement (this.parentDiv, 'ov_panel_button'); this.buttonText = AddDiv (this.button, 'ov_panel_button_text'); AddSvgIconElement (this.button, 'arrow_right', 'ov_panel_button_icon'); this.button.addEventListener ('click', () => { diff --git a/source/website/panelset.js b/source/website/panelset.js index c9c9519..5cc41d8 100644 --- a/source/website/panelset.js +++ b/source/website/panelset.js @@ -1,5 +1,5 @@ import { AddDiv, ShowDomElement, IsDomElementVisible, SetDomElementWidth, SetDomElementHeight } from '../engine/viewer/domutils.js'; -import { AddSvgIconElement, SetSvgIconImageElement } from './utils.js'; +import { AddSvgIconButtonElement, SetSvgIconImageElement } from './utils.js'; export class Panel { @@ -74,9 +74,7 @@ export class PanelSet AddPanel (panel) { this.panels.push (panel); - let button = AddSvgIconElement (this.menuDiv, panel.GetIcon (), 'ov_panel_set_menu_button'); - button.setAttribute ('alt', panel.GetName ()); - button.setAttribute ('title', panel.GetName ()); + let button = AddSvgIconButtonElement (this.menuDiv, panel.GetIcon (), 'ov_panel_set_menu_button', panel.GetName ()); this.panelButtons.push (button); button.addEventListener ('click', () => { if (panel === this.GetVisiblePanel ()) { @@ -110,6 +108,7 @@ export class PanelSet } else { for (let panelButton of this.panelButtons) { panelButton.classList.remove ('selected'); + panelButton.setAttribute ('aria-pressed', 'false'); } for (let panel of this.panels) { panel.Show (false); @@ -133,9 +132,11 @@ export class PanelSet for (let otherPanelButton of this.panelButtons) { if (otherPanelButton !== panelButton) { otherPanelButton.classList.remove ('selected'); + otherPanelButton.setAttribute ('aria-pressed', 'false'); } } panelButton.classList.add ('selected'); + panelButton.setAttribute ('aria-pressed', 'true'); for (let otherPanel of this.panels) { if (otherPanel !== panel) { diff --git a/source/website/sharingdialog.js b/source/website/sharingdialog.js index 7a7ed13..b5d196f 100644 --- a/source/website/sharingdialog.js +++ b/source/website/sharingdialog.js @@ -1,10 +1,11 @@ import { FileSource } from '../engine/io/fileutils.js'; -import { AddDiv, AddDomElement } from '../engine/viewer/domutils.js'; +import { AddDiv, AddDomElement, AddButtonElement } from '../engine/viewer/domutils.js'; import { AddCheckbox } from '../website/utils.js'; import { CreateUrlBuilder } from '../engine/parameters/parameterlist.js'; import { ShowMessageDialog } from './dialogs.js'; import { ButtonDialog } from './dialog.js'; import { CopyToClipboard } from './utils.js'; +import { SetElementAccessibleName } from './utils.js'; import { HandleEvent } from './eventhandler.js'; import { Loc } from '../engine/core/localization.js'; @@ -26,12 +27,15 @@ export function ShowSharingDialog (fileList, settings, viewer) let input = AddDomElement (container, 'input', null); input.setAttribute ('type', 'text'); input.readOnly = true; - let button = AddDiv (container, 'ov_button outline ov_dialog_copyable_input_button', copyText); + let button = AddButtonElement (container, 'ov_button outline ov_dialog_copyable_input_button', copyText); + SetElementAccessibleName (button, copyText); button.addEventListener ('click', () => { CopyToClipboard (getText ()); button.innerHTML = copiedText; + SetElementAccessibleName (button, copiedText); setTimeout (() => { button.innerHTML = copyText; + SetElementAccessibleName (button, copyText); }, 2000); }); return input; diff --git a/source/website/sidebardetailspanel.js b/source/website/sidebardetailspanel.js index 294331b..747a5a0 100644 --- a/source/website/sidebardetailspanel.js +++ b/source/website/sidebardetailspanel.js @@ -3,9 +3,9 @@ import { SubCoord3D } from '../engine/geometry/coord3d.js'; import { GetBoundingBox, IsTwoManifold } from '../engine/model/modelutils.js'; import { CalculateVolume, CalculateSurfaceArea } from '../engine/model/quantities.js'; import { Property, PropertyToString, PropertyType } from '../engine/model/property.js'; -import { AddDiv, AddDomElement, ClearDomElement } from '../engine/viewer/domutils.js'; +import { AddDiv, AddDomElement, ClearDomElement, AddButtonElement } from '../engine/viewer/domutils.js'; import { SidebarPanel } from './sidebarpanel.js'; -import { CreateInlineColorCircle } from './utils.js'; +import { CreateInlineColorCircle, SetElementAccessibleName } from './utils.js'; import { GetFileName, IsUrl } from '../engine/io/fileutils.js'; import { MaterialSource, MaterialType } from '../engine/model/material.js'; import { RGBColorToHexString } from '../engine/model/color.js'; @@ -170,7 +170,8 @@ export class SidebarDetailsPanel extends SidebarPanel let valueColumn = AddDiv (row, 'ov_property_table_cell ov_property_table_value'); nameColumn.setAttribute ('title', name); - let calculateButton = AddDiv (valueColumn, 'ov_property_table_button', Loc ('Calculate...')); + let calculateButton = AddButtonElement (valueColumn, 'ov_property_table_button', Loc ('Calculate...')); + SetElementAccessibleName (calculateButton, name); calculateButton.addEventListener ('click', () => { ClearDomElement (valueColumn); valueColumn.innerHTML = Loc ('Please wait...'); diff --git a/source/website/sidebarsettingspanel.js b/source/website/sidebarsettingspanel.js index 5332ee1..af7d889 100644 --- a/source/website/sidebarsettingspanel.js +++ b/source/website/sidebarsettingspanel.js @@ -1,6 +1,6 @@ import { RGBColor, RGBColorToHexString, RGBAColor, RGBAColorToHexString, ColorComponentFromFloat } from '../engine/model/color.js'; -import { AddDiv, AddDomElement, ShowDomElement, SetDomElementOuterHeight } from '../engine/viewer/domutils.js'; -import { AddRangeSlider, AddToggle, AddCheckbox } from '../website/utils.js'; +import { AddDiv, AddDomElement, ShowDomElement, SetDomElementOuterHeight, AddButtonElement } from '../engine/viewer/domutils.js'; +import { AddRangeSlider, AddToggle, AddCheckbox, SetElementAccessibleName, MakeElementButtonLike } from '../website/utils.js'; import { CalculatePopupPositionToElementTopLeft } from './dialogs.js'; import { PopupDialog } from './dialog.js'; import { Settings } from './settings.js'; @@ -107,7 +107,7 @@ class EnvironmentMapPopup extends PopupDialog if (isSelected) { envMapImage.element.classList.add ('selected'); } - envMapImage.element.addEventListener ('click', () => { + let onSelect = () => { for (let otherImage of envMapImages) { otherImage.element.classList.remove ('selected'); } @@ -120,7 +120,9 @@ class EnvironmentMapPopup extends PopupDialog settings.environmentMapName = envMapImage.name; } callbacks.onEnvironmentMapChanged (); - }); + }; + MakeElementButtonLike (envMapImage.element, envMapImage.name, onSelect); + envMapImage.element.addEventListener ('click', onSelect); } } else if (shadingType === ShadingType.Physical) { let isPerspective = (callbacks.getProjectionMode () === ProjectionMode.Perspective); @@ -138,14 +140,16 @@ class EnvironmentMapPopup extends PopupDialog if (envMapImage.name === settings.environmentMapName) { envMapImage.element.classList.add ('selected'); } - envMapImage.element.addEventListener ('click', () => { + let onSelect = () => { for (let otherImage of envMapImages) { otherImage.element.classList.remove ('selected'); } envMapImage.element.classList.add ('selected'); settings.environmentMapName = envMapImage.name; callbacks.onEnvironmentMapChanged (); - }); + }; + MakeElementButtonLike (envMapImage.element, envMapImage.name, onSelect); + envMapImage.element.addEventListener ('click', onSelect); } } @@ -229,7 +233,8 @@ class SettingsModelDisplaySection extends SettingsSection }); this.environmentMapPhongDiv = AddDiv (this.contentDiv, 'ov_sidebar_parameter'); - this.environmentMapPhongInput = AddDiv (this.environmentMapPhongDiv, 'ov_sidebar_image_picker'); + this.environmentMapPhongInput = AddButtonElement (this.environmentMapPhongDiv, 'ov_sidebar_image_picker'); + SetElementAccessibleName (this.environmentMapPhongInput, Loc ('Background Image')); AddDiv (this.environmentMapPhongDiv, null, Loc ('Background Image')); this.environmentMapPhongInput.addEventListener ('click', () => { this.environmentMapPopup = new EnvironmentMapPopup (); @@ -245,7 +250,8 @@ class SettingsModelDisplaySection extends SettingsSection }); this.environmentMapPbrDiv = AddDiv (this.contentDiv, 'ov_sidebar_parameter'); - this.environmentMapPbrInput = AddDiv (this.environmentMapPbrDiv, 'ov_sidebar_image_picker'); + this.environmentMapPbrInput = AddButtonElement (this.environmentMapPbrDiv, 'ov_sidebar_image_picker'); + SetElementAccessibleName (this.environmentMapPbrInput, Loc ('Environment')); AddDiv (this.environmentMapPbrDiv, null, Loc ('Environment')); this.environmentMapPbrInput.addEventListener ('click', () => { this.environmentMapPopup = new EnvironmentMapPopup (); @@ -263,7 +269,7 @@ class SettingsModelDisplaySection extends SettingsSection this.UpdateEnvironmentMap (); let edgeParameterDiv = AddDiv (this.contentDiv, 'ov_sidebar_parameter'); - this.edgeDisplayToggle = AddToggle (edgeParameterDiv, 'ov_sidebar_parameter_toggle'); + this.edgeDisplayToggle = AddToggle (edgeParameterDiv, 'ov_sidebar_parameter_toggle', Loc ('Show Edges')); AddDiv (edgeParameterDiv, 'ov_sidebar_parameter_text', Loc ('Show Edges')); this.edgeSettingsDiv = AddDiv (this.contentDiv, 'ov_sidebar_settings_padded'); @@ -458,7 +464,8 @@ export class SidebarSettingsPanel extends SidebarPanel this.modelDisplaySection = new SettingsModelDisplaySection (this.sectionsDiv, this.settings); this.importParametersSection = new SettingsImportParametersSection (this.sectionsDiv, this.settings); - this.resetToDefaultsButton = AddDiv (this.contentDiv, 'ov_button ov_panel_button outline', 'Reset to Default'); + this.resetToDefaultsButton = AddButtonElement (this.contentDiv, 'ov_button ov_panel_button outline', 'Reset to Default'); + SetElementAccessibleName (this.resetToDefaultsButton, Loc ('Reset to Default')); this.resetToDefaultsButton.addEventListener ('click', () => { this.ResetToDefaults (); }); diff --git a/source/website/threemodelloaderui.js b/source/website/threemodelloaderui.js index ed744a2..499e80f 100644 --- a/source/website/threemodelloaderui.js +++ b/source/website/threemodelloaderui.js @@ -1,8 +1,8 @@ -import { AddDiv } from '../engine/viewer/domutils.js'; +import { AddDiv, AddButtonElement } from '../engine/viewer/domutils.js'; import { ThreeModelLoader } from '../engine/threejs/threemodelloader.js'; import { ShowMessageDialog } from './dialogs.js'; import { ButtonDialog, ProgressDialog } from './dialog.js'; -import { AddSvgIconElement } from './utils.js'; +import { AddSvgIconElement, SetElementAccessibleName } from './utils.js'; import { ImportErrorCode } from '../engine/import/importer.js'; import { Loc } from '../engine/core/localization.js'; @@ -124,7 +124,8 @@ export class ThreeModelLoaderUI for (let i = 0; i < fileNames.length; i++) { let fileName = fileNames[i]; - let fileLink = AddDiv (fileList, 'ov_dialog_file_link'); + let fileLink = AddButtonElement (fileList, 'ov_dialog_file_link'); + SetElementAccessibleName (fileLink, fileName); AddSvgIconElement (fileLink, 'meshes', 'ov_file_link_img'); AddDiv (fileLink, 'ov_dialog_file_link_text', fileName); fileLink.addEventListener ('click', () => { diff --git a/source/website/toolbar.js b/source/website/toolbar.js index f5d3271..f757a9f 100644 --- a/source/website/toolbar.js +++ b/source/website/toolbar.js @@ -1,5 +1,5 @@ -import { AddDiv, CreateDiv } from '../engine/viewer/domutils.js'; -import { AddSvgIconElement, InstallTooltip } from './utils.js'; +import { AddDiv, CreateButtonElement } from '../engine/viewer/domutils.js'; +import { AddSvgIconElement, InstallTooltip, SetElementAccessibleName } from './utils.js'; export class ToolbarButton { @@ -9,13 +9,13 @@ export class ToolbarButton this.imageTitle = imageTitle; this.selected = false; - this.buttonDiv = CreateDiv ('ov_toolbar_button'); + this.buttonDiv = CreateButtonElement ('ov_toolbar_button'); this.buttonImg = AddSvgIconElement (this.buttonDiv, this.image); if (onClick !== null) { this.buttonDiv.addEventListener ('click', onClick); } - this.buttonDiv.setAttribute ('alt', this.imageTitle); + SetElementAccessibleName (this.buttonDiv, this.imageTitle); InstallTooltip (this.buttonDiv, this.imageTitle); } @@ -57,6 +57,7 @@ export class ToolbarButton } else { this.buttonDiv.classList.remove ('selected'); } + this.buttonDiv.setAttribute ('aria-pressed', this.selected ? 'true' : 'false'); } } diff --git a/source/website/utils.js b/source/website/utils.js index a4bf597..97e1b06 100644 --- a/source/website/utils.js +++ b/source/website/utils.js @@ -1,6 +1,6 @@ import { RGBColor, RGBColorToHexString } from '../engine/model/color.js'; import { CreateObjectUrl } from '../engine/io/bufferutils.js'; -import { AddDiv, CreateDiv, AddDomElement } from '../engine/viewer/domutils.js'; +import { AddDiv, CreateDiv, AddDomElement, AddButtonElement, CreateButtonElement } from '../engine/viewer/domutils.js'; import { Loc } from '../engine/core/localization.js'; import { Theme } from './settings.js'; @@ -128,6 +128,7 @@ export function DownloadArrayBufferAsFile (arrayBuffer, fileName) export function CreateSvgIconElement (iconName, className) { let iconDiv = CreateDiv ('ov_svg_icon'); + iconDiv.setAttribute ('aria-hidden', 'true'); if (className) { iconDiv.classList.add (className); } @@ -135,6 +136,29 @@ export function CreateSvgIconElement (iconName, className) return iconDiv; } +export function SetElementAccessibleName (element, text) +{ + element.setAttribute ('aria-label', text); + element.setAttribute ('title', text); +} + +export function CreateSvgIconButtonElement (iconName, className, accessibleName) +{ + let button = CreateButtonElement (className); + if (accessibleName) { + SetElementAccessibleName (button, accessibleName); + } + AddSvgIconElement (button, iconName); + return button; +} + +export function AddSvgIconButtonElement (parentElement, iconName, className, accessibleName) +{ + let button = CreateSvgIconButtonElement (iconName, className, accessibleName); + parentElement.appendChild (button); + return button; +} + export function AddSvgIconElement (parentElement, iconName, className) { let iconDiv = CreateSvgIconElement (iconName, className); @@ -148,6 +172,21 @@ export function SetSvgIconImageElement (iconElement, iconName) iconDiv.className = 'icon icon-' + iconName; } +export function MakeElementButtonLike (element, accessibleName, onActivate) +{ + element.setAttribute ('role', 'button'); + element.setAttribute ('tabindex', '0'); + if (accessibleName) { + SetElementAccessibleName (element, accessibleName); + } + element.addEventListener ('keydown', (ev) => { + if (ev.key === 'Enter' || ev.key === ' ') { + ev.preventDefault (); + onActivate (ev); + } + }); +} + export function CreateInlineColorCircle (color) { let hexString = '#' + RGBColorToHexString (color); @@ -158,6 +197,7 @@ export function CreateInlineColorCircle (color) ); let darkerColorHexString = '#' + RGBColorToHexString (darkerColor); let circleDiv = CreateDiv ('ov_color_circle'); + circleDiv.setAttribute ('aria-hidden', 'true'); circleDiv.style.background = hexString; circleDiv.style.border = '1px solid ' + darkerColorHexString; return circleDiv; @@ -320,7 +360,7 @@ export function AddSelect (parentElement, options, selectedIndex, onChange) return select; } -export function AddToggle (parentElement, className) +export function AddToggle (parentElement, className, accessibleName) { function UpdateStatus (toggle, status) { @@ -329,6 +369,7 @@ export function AddToggle (parentElement, className) } else { toggle.classList.remove ('on'); } + toggle.setAttribute ('aria-pressed', status ? 'true' : 'false'); } let status = false; @@ -338,7 +379,10 @@ export function AddToggle (parentElement, className) if (className) { toggleClassName += ' ' + className; } - let toggle = AddDiv (parentElement, toggleClassName); + let toggle = AddButtonElement (parentElement, toggleClassName); + if (accessibleName) { + SetElementAccessibleName (toggle, accessibleName); + } AddDiv (toggle, 'ov_toggle_slider'); toggle.addEventListener ('click', () => { diff --git a/website/index.html b/website/index.html index c06fceb..a585752 100644 --- a/website/index.html +++ b/website/index.html @@ -1,9 +1,10 @@ - +
- + + @@ -14,25 +15,29 @@ - + @@ -42,7 +47,7 @@