Fossil SCM
fossil.tabs API now injects a FIELDSET wrapper around all tabs so that we can disable all input elements on a tab by disabling the fieldset, the goal being to disable access to hotkeys which are mapped to elements which are in any tab other than the current one.
Commit
33610b04de8fdf561a4b3cffed40353f008c87ed1d27a15f6b8c8731146265b4
Parent
b12cae857cdadbd…
2 files changed
+14
-1
+32
-6
+14
-1
| --- src/default.css | ||
| +++ src/default.css | ||
| @@ -970,14 +970,27 @@ | ||
| 970 | 970 | flex-direction: column; |
| 971 | 971 | border-width: 1px; |
| 972 | 972 | border-style: outset; |
| 973 | 973 | border-color: inherit; |
| 974 | 974 | } |
| 975 | -.tab-container > .tabs > .tab-panel { | |
| 975 | +.tab-container > .tabs > .tab-panel, | |
| 976 | +.tab-container > .tabs > fieldset.tab-wrapper { | |
| 977 | + align-self: stretch; | |
| 978 | + flex: 10 1 auto; | |
| 979 | + display: flex; | |
| 980 | + flex-direction: row; | |
| 981 | + border: 0; | |
| 982 | + padding: 0; | |
| 983 | + margin: 0; | |
| 984 | +} | |
| 985 | +.tab-container > .tabs > fieldset.tab-wrapper > .tab-panel{ | |
| 976 | 986 | align-self: stretch; |
| 977 | 987 | flex: 10 1 auto; |
| 978 | 988 | display: block; |
| 989 | + border: 0; | |
| 990 | + padding: 0; | |
| 991 | + margin: 0; | |
| 979 | 992 | } |
| 980 | 993 | .tab-container > .tab-bar { |
| 981 | 994 | display: flex; |
| 982 | 995 | flex-direction: row; |
| 983 | 996 | flex: 1 10 auto; |
| 984 | 997 |
| --- src/default.css | |
| +++ src/default.css | |
| @@ -970,14 +970,27 @@ | |
| 970 | flex-direction: column; |
| 971 | border-width: 1px; |
| 972 | border-style: outset; |
| 973 | border-color: inherit; |
| 974 | } |
| 975 | .tab-container > .tabs > .tab-panel { |
| 976 | align-self: stretch; |
| 977 | flex: 10 1 auto; |
| 978 | display: block; |
| 979 | } |
| 980 | .tab-container > .tab-bar { |
| 981 | display: flex; |
| 982 | flex-direction: row; |
| 983 | flex: 1 10 auto; |
| 984 |
| --- src/default.css | |
| +++ src/default.css | |
| @@ -970,14 +970,27 @@ | |
| 970 | flex-direction: column; |
| 971 | border-width: 1px; |
| 972 | border-style: outset; |
| 973 | border-color: inherit; |
| 974 | } |
| 975 | .tab-container > .tabs > .tab-panel, |
| 976 | .tab-container > .tabs > fieldset.tab-wrapper { |
| 977 | align-self: stretch; |
| 978 | flex: 10 1 auto; |
| 979 | display: flex; |
| 980 | flex-direction: row; |
| 981 | border: 0; |
| 982 | padding: 0; |
| 983 | margin: 0; |
| 984 | } |
| 985 | .tab-container > .tabs > fieldset.tab-wrapper > .tab-panel{ |
| 986 | align-self: stretch; |
| 987 | flex: 10 1 auto; |
| 988 | display: block; |
| 989 | border: 0; |
| 990 | padding: 0; |
| 991 | margin: 0; |
| 992 | } |
| 993 | .tab-container > .tab-bar { |
| 994 | display: flex; |
| 995 | flex-direction: row; |
| 996 | flex: 1 10 auto; |
| 997 |
+32
-6
| --- src/fossil.tabs.js | ||
| +++ src/fossil.tabs.js | ||
| @@ -12,37 +12,60 @@ | ||
| 12 | 12 | this.e = {}; |
| 13 | 13 | if(domElem) this.init(domElem); |
| 14 | 14 | }; |
| 15 | 15 | |
| 16 | 16 | /** |
| 17 | - Internal helper to normalize a method argument | |
| 18 | - to a tab element. | |
| 17 | + Internal helper to normalize a method argument to a tab | |
| 18 | + element. arg may be a tab DOM element or an index into | |
| 19 | + tabMgr.e.tabs.childNodes. Returns the corresponding tab element. | |
| 19 | 20 | */ |
| 20 | 21 | const tabArg = function(arg,tabMgr){ |
| 21 | 22 | if('string'===typeof arg) arg = E(arg); |
| 22 | 23 | else if(tabMgr && 'number'===typeof arg && arg>=0){ |
| 23 | 24 | arg = tabMgr.e.tabs.childNodes[arg]; |
| 25 | + } | |
| 26 | + if(arg){ | |
| 27 | + if('FIELDSET'===arg.tagName && arg.classList.contains('tab-wrapper')){ | |
| 28 | + arg = arg.firstElementChild; | |
| 29 | + } | |
| 24 | 30 | } |
| 25 | 31 | return arg; |
| 26 | 32 | }; |
| 27 | 33 | |
| 34 | + | |
| 35 | + /** | |
| 36 | + Sets sets the visibility of tab element e to on or off. e MUST be | |
| 37 | + a TabManager tab element which has been wrapped in a | |
| 38 | + FIELDSET.tab-wrapper parent element. We disable the hidden | |
| 39 | + FIELDSET.tab-wrapper elements so that any access keys assigned | |
| 40 | + to their children cannot be inadvertently triggered | |
| 41 | + */ | |
| 28 | 42 | const setVisible = function(e,yes){ |
| 29 | - D[yes ? 'removeClass' : 'addClass'](e, 'hidden'); | |
| 43 | + const fsWrapper = e.parentElement/*FIELDSET wrapper*/; | |
| 44 | + if(yes){ | |
| 45 | + D.removeClass(e, 'hidden'); | |
| 46 | + D.enable(fsWrapper); | |
| 47 | + }else{ | |
| 48 | + D.addClass(e, 'hidden'); | |
| 49 | + D.disable(fsWrapper); | |
| 50 | + } | |
| 30 | 51 | }; |
| 31 | 52 | |
| 32 | 53 | TabManager.prototype = { |
| 33 | 54 | /** |
| 34 | 55 | Initializes the tabs associated with the given tab container |
| 35 | 56 | (DOM element or selector for a single element). This must be |
| 36 | 57 | called once before using any other member functions of a given |
| 37 | 58 | instance, noting that the constructor will call this if it is |
| 38 | - passed an argument. | |
| 59 | + passed an argument. | |
| 39 | 60 | |
| 40 | 61 | The tab container must have an 'id' attribute. This function |
| 41 | 62 | looks through the DOM for all elements which have |
| 42 | 63 | data-tab-parent=thatId. For each one it creates a button to |
| 43 | - switch to that tab and moves the element into this.e.tabs. | |
| 64 | + switch to that tab and moves the element into this.e.tabs, | |
| 65 | + *possibly* injecting an intermediary element between | |
| 66 | + this.e.tabs and the element. | |
| 44 | 67 | |
| 45 | 68 | The label for each tab is set by the data-tab-label attribute |
| 46 | 69 | of each element, defaulting to something not terribly useful. |
| 47 | 70 | |
| 48 | 71 | When it's done, it auto-selects the first tab unless a tab has |
| @@ -117,11 +140,13 @@ | ||
| 117 | 140 | e.target.$manager.switchToTab(e.target.$tab); |
| 118 | 141 | }; |
| 119 | 142 | } |
| 120 | 143 | tab = tabArg(tab); |
| 121 | 144 | tab.remove(); |
| 122 | - D.append(this.e.tabs, D.addClass(tab,'tab-panel')); | |
| 145 | + const eFs = D.addClass(D.fieldset(), 'tab-wrapper'); | |
| 146 | + D.append(eFs, D.addClass(tab,'tab-panel')); | |
| 147 | + D.append(this.e.tabs, eFs); | |
| 123 | 148 | const lbl = tab.dataset.tabLabel || 'Tab #'+(this.e.tabs.childNodes.length-1); |
| 124 | 149 | const btn = D.addClass(D.append(D.span(), lbl), 'tab-button'); |
| 125 | 150 | D.append(this.e.tabBar,btn); |
| 126 | 151 | btn.$manager = this; |
| 127 | 152 | btn.$tab = tab; |
| @@ -185,10 +210,11 @@ | ||
| 185 | 210 | this._dispatchEvent('before-switch-from', this._currentTab); |
| 186 | 211 | } |
| 187 | 212 | delete this._currentTab; |
| 188 | 213 | this.e.tabs.childNodes.forEach((e,ndx)=>{ |
| 189 | 214 | const btn = this.e.tabBar.childNodes[ndx]; |
| 215 | + e = e.firstElementChild /* b/c arguments[0] is a FIELDSET wrapper */; | |
| 190 | 216 | if(e===tab){ |
| 191 | 217 | if(D.hasClass(e,'selected')){ |
| 192 | 218 | return; |
| 193 | 219 | } |
| 194 | 220 | self._dispatchEvent('before-switch-to',tab); |
| 195 | 221 |
| --- src/fossil.tabs.js | |
| +++ src/fossil.tabs.js | |
| @@ -12,37 +12,60 @@ | |
| 12 | this.e = {}; |
| 13 | if(domElem) this.init(domElem); |
| 14 | }; |
| 15 | |
| 16 | /** |
| 17 | Internal helper to normalize a method argument |
| 18 | to a tab element. |
| 19 | */ |
| 20 | const tabArg = function(arg,tabMgr){ |
| 21 | if('string'===typeof arg) arg = E(arg); |
| 22 | else if(tabMgr && 'number'===typeof arg && arg>=0){ |
| 23 | arg = tabMgr.e.tabs.childNodes[arg]; |
| 24 | } |
| 25 | return arg; |
| 26 | }; |
| 27 | |
| 28 | const setVisible = function(e,yes){ |
| 29 | D[yes ? 'removeClass' : 'addClass'](e, 'hidden'); |
| 30 | }; |
| 31 | |
| 32 | TabManager.prototype = { |
| 33 | /** |
| 34 | Initializes the tabs associated with the given tab container |
| 35 | (DOM element or selector for a single element). This must be |
| 36 | called once before using any other member functions of a given |
| 37 | instance, noting that the constructor will call this if it is |
| 38 | passed an argument. |
| 39 | |
| 40 | The tab container must have an 'id' attribute. This function |
| 41 | looks through the DOM for all elements which have |
| 42 | data-tab-parent=thatId. For each one it creates a button to |
| 43 | switch to that tab and moves the element into this.e.tabs. |
| 44 | |
| 45 | The label for each tab is set by the data-tab-label attribute |
| 46 | of each element, defaulting to something not terribly useful. |
| 47 | |
| 48 | When it's done, it auto-selects the first tab unless a tab has |
| @@ -117,11 +140,13 @@ | |
| 117 | e.target.$manager.switchToTab(e.target.$tab); |
| 118 | }; |
| 119 | } |
| 120 | tab = tabArg(tab); |
| 121 | tab.remove(); |
| 122 | D.append(this.e.tabs, D.addClass(tab,'tab-panel')); |
| 123 | const lbl = tab.dataset.tabLabel || 'Tab #'+(this.e.tabs.childNodes.length-1); |
| 124 | const btn = D.addClass(D.append(D.span(), lbl), 'tab-button'); |
| 125 | D.append(this.e.tabBar,btn); |
| 126 | btn.$manager = this; |
| 127 | btn.$tab = tab; |
| @@ -185,10 +210,11 @@ | |
| 185 | this._dispatchEvent('before-switch-from', this._currentTab); |
| 186 | } |
| 187 | delete this._currentTab; |
| 188 | this.e.tabs.childNodes.forEach((e,ndx)=>{ |
| 189 | const btn = this.e.tabBar.childNodes[ndx]; |
| 190 | if(e===tab){ |
| 191 | if(D.hasClass(e,'selected')){ |
| 192 | return; |
| 193 | } |
| 194 | self._dispatchEvent('before-switch-to',tab); |
| 195 |
| --- src/fossil.tabs.js | |
| +++ src/fossil.tabs.js | |
| @@ -12,37 +12,60 @@ | |
| 12 | this.e = {}; |
| 13 | if(domElem) this.init(domElem); |
| 14 | }; |
| 15 | |
| 16 | /** |
| 17 | Internal helper to normalize a method argument to a tab |
| 18 | element. arg may be a tab DOM element or an index into |
| 19 | tabMgr.e.tabs.childNodes. Returns the corresponding tab element. |
| 20 | */ |
| 21 | const tabArg = function(arg,tabMgr){ |
| 22 | if('string'===typeof arg) arg = E(arg); |
| 23 | else if(tabMgr && 'number'===typeof arg && arg>=0){ |
| 24 | arg = tabMgr.e.tabs.childNodes[arg]; |
| 25 | } |
| 26 | if(arg){ |
| 27 | if('FIELDSET'===arg.tagName && arg.classList.contains('tab-wrapper')){ |
| 28 | arg = arg.firstElementChild; |
| 29 | } |
| 30 | } |
| 31 | return arg; |
| 32 | }; |
| 33 | |
| 34 | |
| 35 | /** |
| 36 | Sets sets the visibility of tab element e to on or off. e MUST be |
| 37 | a TabManager tab element which has been wrapped in a |
| 38 | FIELDSET.tab-wrapper parent element. We disable the hidden |
| 39 | FIELDSET.tab-wrapper elements so that any access keys assigned |
| 40 | to their children cannot be inadvertently triggered |
| 41 | */ |
| 42 | const setVisible = function(e,yes){ |
| 43 | const fsWrapper = e.parentElement/*FIELDSET wrapper*/; |
| 44 | if(yes){ |
| 45 | D.removeClass(e, 'hidden'); |
| 46 | D.enable(fsWrapper); |
| 47 | }else{ |
| 48 | D.addClass(e, 'hidden'); |
| 49 | D.disable(fsWrapper); |
| 50 | } |
| 51 | }; |
| 52 | |
| 53 | TabManager.prototype = { |
| 54 | /** |
| 55 | Initializes the tabs associated with the given tab container |
| 56 | (DOM element or selector for a single element). This must be |
| 57 | called once before using any other member functions of a given |
| 58 | instance, noting that the constructor will call this if it is |
| 59 | passed an argument. |
| 60 | |
| 61 | The tab container must have an 'id' attribute. This function |
| 62 | looks through the DOM for all elements which have |
| 63 | data-tab-parent=thatId. For each one it creates a button to |
| 64 | switch to that tab and moves the element into this.e.tabs, |
| 65 | *possibly* injecting an intermediary element between |
| 66 | this.e.tabs and the element. |
| 67 | |
| 68 | The label for each tab is set by the data-tab-label attribute |
| 69 | of each element, defaulting to something not terribly useful. |
| 70 | |
| 71 | When it's done, it auto-selects the first tab unless a tab has |
| @@ -117,11 +140,13 @@ | |
| 140 | e.target.$manager.switchToTab(e.target.$tab); |
| 141 | }; |
| 142 | } |
| 143 | tab = tabArg(tab); |
| 144 | tab.remove(); |
| 145 | const eFs = D.addClass(D.fieldset(), 'tab-wrapper'); |
| 146 | D.append(eFs, D.addClass(tab,'tab-panel')); |
| 147 | D.append(this.e.tabs, eFs); |
| 148 | const lbl = tab.dataset.tabLabel || 'Tab #'+(this.e.tabs.childNodes.length-1); |
| 149 | const btn = D.addClass(D.append(D.span(), lbl), 'tab-button'); |
| 150 | D.append(this.e.tabBar,btn); |
| 151 | btn.$manager = this; |
| 152 | btn.$tab = tab; |
| @@ -185,10 +210,11 @@ | |
| 210 | this._dispatchEvent('before-switch-from', this._currentTab); |
| 211 | } |
| 212 | delete this._currentTab; |
| 213 | this.e.tabs.childNodes.forEach((e,ndx)=>{ |
| 214 | const btn = this.e.tabBar.childNodes[ndx]; |
| 215 | e = e.firstElementChild /* b/c arguments[0] is a FIELDSET wrapper */; |
| 216 | if(e===tab){ |
| 217 | if(D.hasClass(e,'selected')){ |
| 218 | return; |
| 219 | } |
| 220 | self._dispatchEvent('before-switch-to',tab); |
| 221 |