From e7d80fcfa91f92c712110d58151df0f8f1e6ed86 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hugo=20H=C3=B6rnquist?= Date: Sun, 21 Nov 2021 16:08:08 +0100 Subject: Rework popup components. Previously popups were driven through some CSS hacks, which used labels with specific positioning, and z-index changes. This never really worked, and led the rest of the tree to be unmanagable. This commit replaces that system with a simpler one, which is being driven by javascript. This also allowed a much simpler tree, which allowed us to - make the popups rezisable (with a resize anchor) - move the window handle to above (configurable) - Add and remove tabs without having manually reflow where all labels are --- static/_global.scss | 11 ++ static/components/popup-element.ts | 86 ++++++++------- static/components/tab-element.ts | 28 ----- static/dragable.ts | 4 +- static/elements.ts | 2 - static/style.scss | 218 +++++++++++++++++-------------------- 6 files changed, 160 insertions(+), 189 deletions(-) delete mode 100644 static/components/tab-element.ts (limited to 'static') diff --git a/static/_global.scss b/static/_global.scss index 8a5bee83..41f426f9 100644 --- a/static/_global.scss +++ b/static/_global.scss @@ -1,5 +1,16 @@ $gray: #757575; $blue: #3399ff; +/* TODO rename this */ $btn-height: 0.5ex; +$tablabel-height: 5ex; +$tablabel-margin: 0; +// "left" or "top" +$popup-style: "left"; + +:root { + /* Each popup can have a different amoutn of tabs. + Override this as appropriate */ + --tabcount: 4; +} diff --git a/static/components/popup-element.ts b/static/components/popup-element.ts index cb67035f..e26ae578 100644 --- a/static/components/popup-element.ts +++ b/static/components/popup-element.ts @@ -7,7 +7,6 @@ import { close_popup, event_from_popup } from '../popup' import { vcal_objects } from '../globals' import { ComponentVEvent } from './vevent' -import { TabElement } from './tab-element' import { remove_event } from '../server_connect' @@ -47,28 +46,58 @@ class PopupElement extends ComponentVEvent { let body = (template.content.cloneNode(true) as DocumentFragment).firstElementChild!; let uid = this.uid; - // console.log(uid); body.getElementsByClassName('populate-with-uid') .forEach((e) => e.setAttribute('data-uid', uid)); - /* tabs */ - // for (let tab of body.querySelectorAll(".tabgroup .tab")) { - // } window.setTimeout(() => { - // let tabs = this.querySelector('tab-element')! - // .shadowRoot! - // .querySelectorAll('label') - // console.log(tabs); - // console.log(this.getElementsByTagName('tab-element')) - for (let tab of this.getElementsByTagName('tab-element')) { - // console.log(tab_container); - // let tab = tab_container.shadowRoot!; - // tab.documentElement.style.setProperty('--i', i); - popuplateTab(tab as TabElement, this.tabgroup_id, this.tabcount) - this.tabcount += 1 + + /* tab change button */ + let tabs = this.querySelectorAll('[role="tab"]') + /* list of all tabs */ + // let tablist = this.querySelector('[role="tablist"]')! + + tabs.forEach(tab => { + tab.addEventListener('click', () => { + + /* hide all tab panels */ + for (let tabcontent of this.querySelectorAll('[role="tabpanel"]')) { + tabcontent.setAttribute('hidden', 'true'); + } + /* unselect all (selected) tab handles */ + for (let item of this.querySelectorAll('[aria-selected="true"]')) { + item.setAttribute('aria-selected', 'false'); + } + /* re-select ourselves */ + tab.setAttribute('aria-selected', 'true'); + + /* unhide our target tab */ + this.querySelector('#' + tab.getAttribute('aria-controls'))! + .removeAttribute('hidden') + }); + }); + + /* tab contents */ + let tabcontents = this.querySelectorAll('[role="tabpanel"]') + + for (let i = 0; i < tabs.length; i++) { + let n = i + this.tabcount; + this.tabgroup_id + let tab = tabs[n]; + let con = tabcontents[n]; + + let a = `${this.tabgroup_id}-tab-${n}` + let b = `${this.tabgroup_id}-con-${n}` + + tab.id = a; + con.setAttribute('aria-labeledby', a); + + con.id = b; + tab.setAttribute('aria-controls', b); + } - (this.querySelector('tab-element label') as HTMLInputElement).click() + this.tabcount += tabs.length + }); /* end tabs */ @@ -126,27 +155,4 @@ class PopupElement extends ComponentVEvent { this.style.left = offsetX + "px"; this.style.top = offsetY + "px"; } - - addTab(tab: TabElement) { - let tabgroup = this.getElementsByClassName('tabgroup')![0]! - tabgroup.append(tab); - popuplateTab(tab, this.tabgroup_id, this.tabcount) - this.tabcount += 1 - } -} - -function popuplateTab(tab: HTMLElement, tabgroup: string, index: number) { - // console.log(tab); - let new_id = gensym(); - let input = tab.querySelector('input[type="radio"]') as HTMLInputElement; - let label = tab.querySelector("label")! - tab.style.setProperty('--tab-index', '' + index); - /* TODO this throws a number of errors, but somehow still works...? */ - if (input !== null) { - input.name = tabgroup - input.id = new_id; - } - if (label !== null) { - label.setAttribute('for', new_id); - } } diff --git a/static/components/tab-element.ts b/static/components/tab-element.ts deleted file mode 100644 index 9da6c504..00000000 --- a/static/components/tab-element.ts +++ /dev/null @@ -1,28 +0,0 @@ -export { TabElement } - -/* */ -class TabElement extends HTMLElement { - constructor() { - super(); - } - - connectedCallback() { - let template - = (document.getElementById('tab-template') as HTMLTemplateElement) - .content - // const shadowRoot = this.attachShadow({ mode: 'open' }) - // .appendChild(template.cloneNode(true)); - - let content = Array.from(this.children, (e) => e.cloneNode(true)) - - this.replaceChildren(template.cloneNode(true)); - - let label = this.querySelector('label') - if (!label) throw "Invalid tab" - - label.setAttribute('title', this.getAttribute('label-title') || '') - label.innerText = this.getAttribute('label') || 'T' - - this.querySelector('slot[name="content"]')!.replaceWith(...content); - } -} diff --git a/static/dragable.ts b/static/dragable.ts index 6110a510..b32bb608 100644 --- a/static/dragable.ts +++ b/static/dragable.ts @@ -18,7 +18,7 @@ function bind_popup_control(nav: HTMLElement) { // throw TypeError('not a popup container'); // } - nav.onmousedown = function(e) { + nav.addEventListener('mousedown', function(e) { /* Ignore mousedown on children */ if (e.target != nav) return; nav.style.cursor = "grabbing"; @@ -27,7 +27,7 @@ function bind_popup_control(nav: HTMLElement) { // let popup = nav.closest(".popup-container"); let popup = nav.closest("popup-element") as HTMLElement; nav.dataset.startPoint = popup.offsetLeft + ";" + popup.offsetTop; - } + }) window.addEventListener('mousemove', function(e) { if (nav.dataset.grabbed) { let [x, y] = nav.dataset.grabPoint!.split(";").map(Number); diff --git a/static/elements.ts b/static/elements.ts index db834fd9..b499556f 100644 --- a/static/elements.ts +++ b/static/elements.ts @@ -4,7 +4,6 @@ import { VEventDL } from './components/vevent-dl' import { ComponentBlock } from './components/vevent-block' import { DateTimeInput } from './components/date-time-input' import { PopupElement } from './components/popup-element' -import { TabElement } from './components/tab-element' import { InputList } from './components/input-list' import { EditRRule } from './components/edit-rrule' @@ -30,5 +29,4 @@ function initialize_components() { /* These maybe also require that the global maps are initialized */ customElements.define('popup-element', PopupElement) - customElements.define('tab-element', TabElement) } diff --git a/static/style.scss b/static/style.scss index 9833a77a..64098283 100644 --- a/static/style.scss +++ b/static/style.scss @@ -24,7 +24,10 @@ html, body { /* main the graphical portion of both the wide and the table view */ - main { + > main { + /* these allow the main area to shrink, so that all areas will fit the + screen. It will however not shrink the elements, leading to our + (desired) scrollbar */ min-width: 0; /* for wide */ min-height: 0; /* for tall */ @@ -742,7 +745,7 @@ vevent-block, .event { .error { border: 3px solid red; background-color: pink; - + pre { padding: 1em; } @@ -766,157 +769,126 @@ popup-element { &.visible { display: block; } -} -.popup { - display: flex; - background-color: #dedede; - color: black; - font-size: 80%; + main { + resize: both; + overflow: auto; - /* overflow-y: auto; */ - max-width: 60ch; - max-height: 60ch; - min-width: 60ch; - min-height: 30ch; + min-height: calc(var(--tabcount) * #{$tablabel-margin + $tablabel-height}); - select { - max-width: 100%; - } + /* some form of sensible minimi and default size for the popup (s main area). */ + min-width: 150px; + width: 350px; + height: 250px; - input { - white-space: initial; - border: 1px solid gray; - max-width: 100%; - } - - .eventtext { - /* makes the text in the popup scroll, but not the sidebar */ - overflow-y: auto; - padding: 1em; - word-break: break-word; - - table { - word-break: initial; - font-size: 65%; + article { + padding: 1em; } } +} - .location { - font-style: initial; - } +.popup-control { + cursor: grab; + background-color: var(--color); - .category { - display: inline-block; - margin-right: 1ex; - } + display: flex; - .popup-control { - display: flex; + @if $popup-style == "left" { flex-direction: column; + padding: 1.2ex; + } @else { + flex-direction: row-reverse; + padding: 1ex; + } - /* not needed, but the icons aren't text - and should therefor not be copied */ - user-select: none; - - cursor: grab; - background-color: var(--color); - /* Transition for background color - * Matches that of '.event'. - * TODO break out to common place */ - transition: 0.3s; - - .btn { - max-width: 2em; - max-height: 2em; - margin: 1em; - display: flex; - align-items: center; - justify-content: center; + button { + display: block; + background: $blue; + color: white; + border: none; + box-shadow: $btn-height $btn-height gray; - font-size: 150%; + &:active { + transform: translate($btn-height, $btn-height); + box-shadow: none; } + @if $popup-style == "left" { + width: 9mm; + height: 9mm; + margin-bottom: 2mm; + } @else { + width: 7mm; + height: 7mm; + margin-left: 1mm; + } } } +.popup-root { + background-color: #dedede; + color: black; -#bar { - width: calc(100% + 2em); - height: 4px; - background: blue; - border-color: blue; - left: -1em; -} + display: flex; -/* Tabs ----------------------------------------- -*/ + @if $popup-style == "left" { + flex-direction: row; + } @else { + flex-direction: column; + } -.tabgroup { - position: relative; - width: 100%; - resize: both; - --tab-size: 6ex; -} -.tab { - > label { + [role="tablist"] { + display: flex; + flex-direction: column; position: absolute; - top: calc(var(--tab-size) * var(--tab-index)); - left: 100%; - /*top: 0; */ - display: block; + margin: 0; + padding: 0; - max-height: 5ex; - min-height: 5ex; + [role="tab"] { + height: $tablabel-height; + margin-bottom: $tablabel-margin; - min-width: 5ex; - width: 5ex; + width: 5ex; + &:hover { + width: 10ex; + } - transition: width 0.1s ease-in-out; - &:hover { - width: 10ex; + transition: width 0.1s ease-in-out; + border: 1px solid #ccc; + border-radius: 0 5px 5px 0; + background-color: #aeaeae; } - border: 1px solid #ccc; - border-radius: 0 5px 5px 0; - background-color: #aeaeae; - - display: flex; - justify-content: center; - align-items: center; - } - - [type=radio] { - display: none; - &:checked ~ label { - z-index: 100; - /* to align all tab */ - border-left: 3px solid transparent; + [aria-selected="true"] { + border-left: none; background-color: #dedede; - - ~ .content { - z-index: 100; - } } } +} - .content { - position: absolute; - top: 0; - left: 0; - background-color: #dedede; - right: 0; - bottom: 0; - overflow: auto; +vevent-edit { - min-width: 100%; - min-height: 100%; + select { + max-width: 100%; + } + + input { + white-space: initial; + border: 1px solid gray; + max-width: 100%; } + .eventtext { + word-break: break-word; + + table { + word-break: initial; + font-size: 65%; + } + } .edit-form { label { @@ -936,10 +908,22 @@ popup-element { .timeinput { } } +} + +vevent-dl { + font-size: 80%; +} +#bar { + width: calc(100% + 2em); + height: 4px; + background: blue; + border-color: blue; + left: -1em; } + .plusminuschecked label { color: black; } -- cgit v1.2.3