Fossil

Check-in [33610b04]
Login

Check-in [33610b04]

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview
Comment: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.
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA3-256: 33610b04de8fdf561a4b3cffed40353f008c87ed1d27a15f6b8c8731146265b4
User & Date: stephan 2020-08-11 15:26:55
References
2020-08-16
10:05
Reverted [33610b04de8fdf56] because a subtle bug in Chrome and FF causes both browsers to break scrolling of elements if they are descendants, however deeply nested, of a fieldset element. The one known workaround for that is too fragile. This is not release-critical. ... (Closed-Leaf check-in: e5c3ffeb user: stephan tags: post-2.12-fixes)
Context
2020-08-11
15:29
fileedit confirmer buttons now use the new pinSize confirmer option. Minor style consistency tweak. fileedit no longer complains when discarding stashed edits when no file is loaded, and reloads the current file only if it was in the now-discarded local edits. ... (check-in: 83a95dbf user: stephan tags: trunk)
15:26
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. ... (check-in: 33610b04 user: stephan tags: trunk)
15:23
Added fossil.confirmer pinSize option which tells it to try to pin the confirmation element's width to the maximum of its initial and awaiting-confirmation widths, to avoid layout reflow while awaiting confirmation. ... (check-in: b12cae85 user: stephan tags: trunk)
Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

Changes to src/default.css.

968
969
970
971
972
973
974
975










976
977
978



979
980
981
982
983
984
985
  margin: 0;
  display: flex;
  flex-direction: column;
  border-width: 1px;
  border-style: outset;
  border-color: inherit;
}
.tab-container > .tabs > .tab-panel {










  align-self: stretch;
  flex: 10 1 auto;
  display: block;



}
.tab-container > .tab-bar {
  display: flex;
  flex-direction: row;
  flex: 1 10 auto;
  align-self: stretch;
  flex-wrap: wrap;







|
>
>
>
>
>
>
>
>
>
>



>
>
>







968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
  margin: 0;
  display: flex;
  flex-direction: column;
  border-width: 1px;
  border-style: outset;
  border-color: inherit;
}
.tab-container > .tabs > .tab-panel,
.tab-container > .tabs > fieldset.tab-wrapper {
  align-self: stretch;
  flex: 10 1 auto;
  display: flex;
  flex-direction: row;
  border: 0;
  padding: 0;
  margin: 0;
}
.tab-container > .tabs > fieldset.tab-wrapper > .tab-panel{
  align-self: stretch;
  flex: 10 1 auto;
  display: block;
  border: 0;
  padding: 0;
  margin: 0;
}
.tab-container > .tab-bar {
  display: flex;
  flex-direction: row;
  flex: 1 10 auto;
  align-self: stretch;
  flex-wrap: wrap;

Changes to src/fossil.tabs.js.

10
11
12
13
14
15
16
17
18

19
20
21
22
23





24
25
26
27








28





29


30
31
32
33
34
35
36
37
38
39
40
41
42
43


44
45
46
47
48
49
50
  */
  const TabManager = function(domElem){
    this.e = {};
    if(domElem) this.init(domElem);
  };

  /**
     Internal helper to normalize a method argument
     to a tab element.

  */
  const tabArg = function(arg,tabMgr){
    if('string'===typeof arg) arg = E(arg);
    else if(tabMgr && 'number'===typeof arg && arg>=0){
      arg = tabMgr.e.tabs.childNodes[arg];





    }
    return arg;
  };









  const setVisible = function(e,yes){





    D[yes ? 'removeClass' : 'addClass'](e, 'hidden');


  };

  TabManager.prototype = {
    /**
       Initializes the tabs associated with the given tab container
       (DOM element or selector for a single element). This must be
       called once before using any other member functions of a given
       instance, noting that the constructor will call this if it is
       passed an argument.       

       The tab container must have an 'id' attribute. This function
       looks through the DOM for all elements which have
       data-tab-parent=thatId. For each one it creates a button to
       switch to that tab and moves the element into this.e.tabs.



       The label for each tab is set by the data-tab-label attribute
       of each element, defaulting to something not terribly useful.

       When it's done, it auto-selects the first tab unless a tab has
       a truthy numeric value in its data-tab-select attribute, in
       which case the last tab to have such a property is selected.







|
|
>





>
>
>
>
>




>
>
>
>
>
>
>
>

>
>
>
>
>
|
>
>








|




|
>
>







10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
  */
  const TabManager = function(domElem){
    this.e = {};
    if(domElem) this.init(domElem);
  };

  /**
     Internal helper to normalize a method argument to a tab
     element. arg may be a tab DOM element or an index into
     tabMgr.e.tabs.childNodes. Returns the corresponding tab element.
  */
  const tabArg = function(arg,tabMgr){
    if('string'===typeof arg) arg = E(arg);
    else if(tabMgr && 'number'===typeof arg && arg>=0){
      arg = tabMgr.e.tabs.childNodes[arg];
    }
    if(arg){
      if('FIELDSET'===arg.tagName && arg.classList.contains('tab-wrapper')){
        arg = arg.firstElementChild;
      }
    }
    return arg;
  };


  /**
    Sets sets the visibility of tab element e to on or off. e MUST be
    a TabManager tab element which has been wrapped in a
    FIELDSET.tab-wrapper parent element. We disable the hidden
    FIELDSET.tab-wrapper elements so that any access keys assigned
    to their children cannot be inadvertently triggered
  */
  const setVisible = function(e,yes){
    const fsWrapper = e.parentElement/*FIELDSET wrapper*/;
    if(yes){
      D.removeClass(e, 'hidden');
      D.enable(fsWrapper);
    }else{
      D.addClass(e, 'hidden');
      D.disable(fsWrapper);
    }
  };

  TabManager.prototype = {
    /**
       Initializes the tabs associated with the given tab container
       (DOM element or selector for a single element). This must be
       called once before using any other member functions of a given
       instance, noting that the constructor will call this if it is
       passed an argument. 

       The tab container must have an 'id' attribute. This function
       looks through the DOM for all elements which have
       data-tab-parent=thatId. For each one it creates a button to
       switch to that tab and moves the element into this.e.tabs,
       *possibly* injecting an intermediary element between
       this.e.tabs and the element.

       The label for each tab is set by the data-tab-label attribute
       of each element, defaulting to something not terribly useful.

       When it's done, it auto-selects the first tab unless a tab has
       a truthy numeric value in its data-tab-select attribute, in
       which case the last tab to have such a property is selected.
115
116
117
118
119
120
121


122
123
124
125
126
127
128
129
      if(!f.click){
        f.click = function(e){
         e.target.$manager.switchToTab(e.target.$tab);
        };
      }
      tab = tabArg(tab);
      tab.remove();


      D.append(this.e.tabs, D.addClass(tab,'tab-panel'));
      const lbl = tab.dataset.tabLabel || 'Tab #'+(this.e.tabs.childNodes.length-1);
      const btn = D.addClass(D.append(D.span(), lbl), 'tab-button');
      D.append(this.e.tabBar,btn);
      btn.$manager = this;
      btn.$tab = tab;
      btn.addEventListener('click', f.click, false);
      return this;







>
>
|







138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
      if(!f.click){
        f.click = function(e){
         e.target.$manager.switchToTab(e.target.$tab);
        };
      }
      tab = tabArg(tab);
      tab.remove();
      const eFs = D.addClass(D.fieldset(), 'tab-wrapper');
      D.append(eFs, D.addClass(tab,'tab-panel'));
      D.append(this.e.tabs, eFs);
      const lbl = tab.dataset.tabLabel || 'Tab #'+(this.e.tabs.childNodes.length-1);
      const btn = D.addClass(D.append(D.span(), lbl), 'tab-button');
      D.append(this.e.tabBar,btn);
      btn.$manager = this;
      btn.$tab = tab;
      btn.addEventListener('click', f.click, false);
      return this;
183
184
185
186
187
188
189

190
191
192
193
194
195
196
      if(tab===this._currentTab) return this;
      else if(this._currentTab){
        this._dispatchEvent('before-switch-from', this._currentTab);
      }
      delete this._currentTab;
      this.e.tabs.childNodes.forEach((e,ndx)=>{
        const btn = this.e.tabBar.childNodes[ndx];

        if(e===tab){
          if(D.hasClass(e,'selected')){
            return;
          }
          self._dispatchEvent('before-switch-to',tab);
          setVisible(e, true);
          this._currentTab = e;







>







208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
      if(tab===this._currentTab) return this;
      else if(this._currentTab){
        this._dispatchEvent('before-switch-from', this._currentTab);
      }
      delete this._currentTab;
      this.e.tabs.childNodes.forEach((e,ndx)=>{
        const btn = this.e.tabBar.childNodes[ndx];
        e = e.firstElementChild /* b/c arguments[0] is a FIELDSET wrapper */;
        if(e===tab){
          if(D.hasClass(e,'selected')){
            return;
          }
          self._dispatchEvent('before-switch-to',tab);
          setVisible(e, true);
          this._currentTab = e;