asciidoctor-tabs 1.0.0.beta.2 → 1.0.0.beta.4

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0e0a17ea54a3c9b7c4c8e152b8cb6c80e4aceb4e7aaa9a03b7cf3dfdf5783d0c
4
- data.tar.gz: e9f4f8844fbdf8435bbb1fc99ac600b35820cabe0a22c4b51902cf39c851e203
3
+ metadata.gz: e73997158c47ea046d9c2016a25cac989e0f0e12e013bdc17f0c3cde8128e55f
4
+ data.tar.gz: 2dd6e9907c7c2498c1ff2226f25d00d654b872644063b02230882a2090f35e45
5
5
  SHA512:
6
- metadata.gz: 0e296393e9638c0131b2e4c1a20ec388944de811295d28c30548befda9d786b5131c2039c2b46096a315218bb4a5335532603b0dd9cb557388f5283af7ddc04d
7
- data.tar.gz: e53ca031e1b78d56b80612403323254cd29ecf38c5a757af1996e3c08fb3a02bbe644ad9010c7a034c55062ec4b4752b669c02978ea6a90c9435d5568f280dce
6
+ metadata.gz: d530e0bb402fd7aa9346c5dc747efe5224436ef8d2e1fd4139a64fb84ed3f3c044069ba0fb807f12ecfe78104d6665395d979aeb1aac77465cef6bb831b1e0ad
7
+ data.tar.gz: c1c756f94280e8d70394c13abdfeb541f226b5eb39cfe86b514211781e5ab80bf1208e7faad6162db45571b26801ada288ae656143d074a7184ba40674afac32
data/CHANGELOG.adoc CHANGED
@@ -4,6 +4,40 @@
4
4
  This document provides a curated view of the changes to Asciidoctor Tabs per release.
5
5
  For a detailed view of what has changed, refer to the {url-repo}/commits/main[commit history] on GitHub.
6
6
 
7
+ == 1.0.0-beta.4 (2023-05-22) - @mojavelinux
8
+
9
+ === Changed
10
+
11
+ * Rework styles for tab to make them compatible with a transition effect; stub in effect in built-in stylesheet
12
+ * Add tab class to tab element (if missing) rather than overwriting className property to preserve existing class names
13
+
14
+ === Fixed
15
+
16
+ * Don't alter state of nested tabs when tab is selected (#55)
17
+ * Don't wrap tables inside nested tabs with tablecontainer div multiple times (#55)
18
+ * Fix fallback logic in behavior script when tab is missing ID or does not match a panel
19
+
20
+ === Details
21
+
22
+ {url-repo}/releases/tag/v1.0.0-beta.4[git tag] | {url-repo}/compare/v1.0.0-beta.3\...v1.0.0-beta.4[full diff]
23
+
24
+ == 1.0.0-beta.3 (2023-02-01) - @mojavelinux
25
+
26
+ === Added
27
+
28
+ * Allow storage of sync tab selection to be configured using `tabs-sync-storage-key` and `tabs-sync-storage-scope` document attributes
29
+ * Add `is-loaded` class to tabs blocks on next tick after initialization for binding transitions (#50)
30
+ * Allow sync group ID to be specified rather than derived using the `sync-group-id` attribute on the tabs block (#52)
31
+
32
+ === Changed
33
+
34
+ * Rename data-sync-group attribute to data-sync-group-id
35
+ * Don't lowercase sync group ID
36
+
37
+ === Details
38
+
39
+ {url-repo}/releases/tag/v1.0.0-beta.3[git tag] | {url-repo}/compare/v1.0.0-beta.2\...v1.0.0-beta.3[full diff]
40
+
7
41
  == 1.0.0-beta.2 (2023-01-30) - @mojavelinux
8
42
 
9
43
  === Changed
data/README.adoc CHANGED
@@ -1,6 +1,6 @@
1
1
  = Asciidoctor Tabs
2
2
  Dan Allen <https://github.com/mojavelinux[@mojavelinux]>
3
- v1.0.0-beta.2, 2023-01-30
3
+ v1.0.0-beta.4, 2023-05-22
4
4
  :idprefix:
5
5
  :idseparator: -
6
6
  ifndef::env-github[:icons: font]
@@ -19,9 +19,10 @@ See the xref:js/README.adoc[README in the js folder] for details.
19
19
 
20
20
  == Overview
21
21
 
22
- Each set of tabs (a "`tabset`") is constructed from a description list (dlist) enclosed in an example block marked with the tabs style (i.e., `[tab]`).
23
- The tabbed interface that this block produces can help to organize information by code language, operating system, or product variant.
22
+ Each set of tabs (aka a "`tabset`" or tabs block) is constructed from a description list (dlist) enclosed in an example block marked with the tabs style (i.e., `[tab]`).
23
+ That nested combination of blocks gets translated by this extension into a single tabs block that is a specialization of an open block.
24
24
 
25
+ The tabbed interface produced from this block can help organize information by code language, operating system, or product variant.
25
26
  The benefit of organizing information in this way is that it condenses the use of vertical space by only showing what's relevant to the reader (and thus hiding information that's irrelevant or redundant).
26
27
  The result is that readers enjoy a better user experience when reading your documentation.
27
28
 
@@ -125,8 +126,10 @@ Tab B:: Contents of tab B in second tabset.
125
126
  ====
126
127
  ----
127
128
 
128
- Note that only tabs blocks with congruent tablists are synchronized.
129
+ Only tabs blocks with congruent tablists are synchronized by default.
129
130
  Each unique combination of tabs implicitly creates a new sync group.
131
+ You can override the sync group ID using the `sync-group-id` attribute on a tabs block to force it to participate in a sync group.
132
+ By default, the sync group ID is derived from the text of each tab, sorted, then joined on `|` (e.g., `A|B`).
130
133
 
131
134
  Alternately, you can set the `sync` option on each tabs block.
132
135
  If you want to delist a tabs block from sync, set the `nosync` option on that block.
@@ -134,6 +137,7 @@ If you want to delist a tabs block from sync, set the `nosync` option on that bl
134
137
  If you want to persist the sync selection, assign a value to the `data-sync-storage-key` attribute on the `<script>` tag.
135
138
  By default, the sync selection will be assigned to the specified key in local storage.
136
139
  You can set the `data-sync-storage-scope` attribute on the `<script>` tag to `session` to use session storage instead.
140
+ When using the extension on a standalone document, you can configure these options using the `tabs-sync-storage-key` and `tabs-sync-storage-scope` document attributes, respectively.
137
141
 
138
142
  == Usage
139
143
 
data/data/css/tabs.css CHANGED
@@ -35,6 +35,16 @@
35
35
  margin-left: 0.25em;
36
36
  }
37
37
 
38
+ .tabs .tablist li::after {
39
+ content: "";
40
+ display: block;
41
+ height: 1px;
42
+ position: absolute;
43
+ bottom: -1px;
44
+ left: 0;
45
+ right: 0;
46
+ }
47
+
38
48
  .tabs.is-loading .tablist li:not(:first-child),
39
49
  .tabs:not(.is-loading) .tablist li:not(.is-selected) {
40
50
  background-color: #f5f5f5;
@@ -42,15 +52,15 @@
42
52
 
43
53
  .tabs.is-loading .tablist li:first-child::after,
44
54
  .tabs:not(.is-loading) .tablist li.is-selected::after {
45
- background-color: inherit;
46
- content: "";
47
- display: block;
48
- height: 3px; /* Chrome doesn't always paint the line accurately, so add a little extra */
49
- position: absolute;
50
- bottom: -1.5px;
51
- left: 0;
52
- right: 0;
55
+ background-color: #fff;
56
+ }
57
+
58
+ /*
59
+ .tabs:not(.is-loading) .tablist li,
60
+ .tabs:not(.is-loading) .tablist li::after {
61
+ transition: background-color 200ms ease-in-out;
53
62
  }
63
+ */
54
64
 
55
65
  .tablist > ul p {
56
66
  line-height: inherit;
data/data/js/tabs.js CHANGED
@@ -12,42 +12,48 @@
12
12
  var syncIds = tabs.classList.contains('is-sync') ? {} : undefined
13
13
  var tablist = tabs.querySelector('.tablist ul')
14
14
  tablist.setAttribute('role', 'tablist')
15
- var initial
15
+ var start
16
16
  forEach.call(tablist.querySelectorAll('li'), function (tab, idx) {
17
- tab.setAttribute('role', (tab.className = 'tab')) // NOTE converter may not have set class on li
17
+ tab.tabIndex = -1
18
+ tab.setAttribute('role', tab.classList.add('tab') || 'tab')
18
19
  var id, anchor, syncId
19
- if (!(id = tab.id)) {
20
- if (!(anchor = tab.querySelector('a[id]'))) return // invalid state
21
- tab.id = id = anchor.parentNode.removeChild(anchor).id
20
+ if (!(id = tab.id) && (anchor = tab.querySelector('a[id]'))) {
21
+ id = tab.id = anchor.parentNode.removeChild(anchor).id
22
22
  }
23
- var panel = tabs.querySelector('.tabpanel[aria-labelledby~="' + id + '"]')
24
- if (!panel) return // invalid state
25
- tab.tabIndex = -1
26
- syncIds && (((syncId = tab.textContent.toLowerCase().trim()) in syncIds) ? (syncId = undefined) : true) &&
23
+ var panel = id && tabs.querySelector('.tabpanel[aria-labelledby~="' + id + '"]')
24
+ if (!panel) return idx ? undefined : toggleSelected(tab, true) // invalid state
25
+ syncIds && (((syncId = tab.textContent.trim()) in syncIds) ? (syncId = undefined) : true) &&
27
26
  (syncIds[(tab.dataset.syncId = syncId)] = tab)
28
- idx || (initial = { tab: tab, panel: panel }) && syncIds ? toggleHidden(panel, true) : toggleSelected(tab, true)
27
+ idx || (syncIds && (start = { tab: tab, panel: panel })) ? toggleHidden(panel, true) : toggleSelected(tab, true)
29
28
  tab.setAttribute('aria-controls', panel.id)
30
29
  panel.setAttribute('role', 'tabpanel')
31
- forEach.call(panel.querySelectorAll('table.tableblock'), function (table) {
32
- var container = Object.assign(document.createElement('div'), { className: 'tablecontainer' })
33
- table.parentNode.insertBefore(container, table).appendChild(table)
34
- })
35
30
  var onClick = syncId === undefined ? activateTab : activateTabSync
36
31
  tab.addEventListener('click', onClick.bind({ tabs: tabs, tab: tab, panel: panel }))
37
32
  })
38
- if (syncIds && initial) {
39
- var syncGroup = tabs.dataset.syncGroup = Object.keys(syncIds).sort().join('|')
33
+ if (!tabs.closest('.tabpanel')) {
34
+ forEach.call(tabs.querySelectorAll('.tabpanel table.tableblock'), function (table) {
35
+ var container = Object.assign(document.createElement('div'), { className: 'tablecontainer' })
36
+ table.parentNode.insertBefore(container, table).appendChild(table)
37
+ })
38
+ }
39
+ if (start) {
40
+ var syncGroupId
41
+ for (var i = 0, lst = tabs.classList, len = lst.length, className; i !== len; i++) {
42
+ if (!(className = lst.item(i)).startsWith('data-sync-group-id=')) continue
43
+ tabs.dataset.syncGroupId = syncGroupId = lst.remove(className) || className.slice(19).replace(/\u00a0/g, ' ')
44
+ break
45
+ }
46
+ if (syncGroupId === undefined) tabs.dataset.syncGroupId = syncGroupId = Object.keys(syncIds).sort().join('|')
40
47
  var preferredSyncId = 'syncStorageKey' in config &&
41
- window[(config.syncStorageScope || 'local') + 'Storage'].getItem(config.syncStorageKey + '-' + syncGroup)
48
+ window[(config.syncStorageScope || 'local') + 'Storage'].getItem(config.syncStorageKey + '-' + syncGroupId)
42
49
  var tab = preferredSyncId && syncIds[preferredSyncId]
43
- tab && Object.assign(initial, { tab: tab, panel: document.getElementById(tab.getAttribute('aria-controls')) })
44
- toggleSelected(initial.tab, true) || toggleHidden(initial.panel, false)
50
+ tab && Object.assign(start, { tab: tab, panel: document.getElementById(tab.getAttribute('aria-controls')) })
51
+ toggleSelected(start.tab, true) || toggleHidden(start.panel, false)
45
52
  }
46
53
  })
47
54
  onHashChange()
48
- forEach.call(tabsBlocks, function (tabs) {
49
- tabs.classList.remove('is-loading')
50
- })
55
+ toggleClassOnEach(tabsBlocks, 'is-loading', 'remove')
56
+ window.setTimeout(toggleClassOnEach.bind(null, tabsBlocks, 'is-loaded', 'add'), 0)
51
57
  window.addEventListener('hashchange', onHashChange)
52
58
  }
53
59
 
@@ -55,14 +61,14 @@
55
61
  var tab = this.tab
56
62
  var tabs = this.tabs || (this.tabs = tab.closest('.tabs'))
57
63
  var panel = this.panel || (this.panel = document.getElementById(tab.getAttribute('aria-controls')))
58
- forEach.call(tabs.querySelectorAll('.tablist .tab'), function (el) {
64
+ querySelectorWithSiblings(tabs, '.tablist .tab', 'tab').forEach(function (el) {
59
65
  toggleSelected(el, el === tab)
60
66
  })
61
- forEach.call(tabs.querySelectorAll('.tabpanel'), function (el) {
67
+ querySelectorWithSiblings(tabs, '.tabpanel', 'tabpanel').forEach(function (el) {
62
68
  toggleHidden(el, el !== panel)
63
69
  })
64
- if (!this.isSync && 'syncStorageKey' in config && 'syncGroup' in tabs.dataset) {
65
- var storageKey = config.syncStorageKey + '-' + tabs.dataset.syncGroup
70
+ if (!this.isSync && 'syncStorageKey' in config && 'syncGroupId' in tabs.dataset) {
71
+ var storageKey = config.syncStorageKey + '-' + tabs.dataset.syncGroupId
66
72
  window[(config.syncStorageScope || 'local') + 'Storage'].setItem(storageKey, tab.dataset.syncId)
67
73
  }
68
74
  if (!e) return
@@ -78,23 +84,36 @@
78
84
  var thisTab = this.tab
79
85
  var initialY = thisTabs.getBoundingClientRect().y
80
86
  forEach.call(document.querySelectorAll('.tabs'), function (tabs) {
81
- if (tabs !== thisTabs && tabs.dataset.syncGroup === thisTabs.dataset.syncGroup) {
82
- forEach.call(tabs.querySelectorAll('.tablist .tab'), function (tab) {
83
- if (tab.dataset.syncId === thisTab.dataset.syncId) activateTab.call({ tabs: tabs, tab: tab, isSync: true })
84
- })
85
- }
87
+ if (tabs === thisTabs || tabs.dataset.syncGroupId !== thisTabs.dataset.syncGroupId) return
88
+ querySelectorWithSiblings(tabs, '.tablist .tab', 'tab').forEach(function (tab) {
89
+ if (tab.dataset.syncId === thisTab.dataset.syncId) activateTab.call({ tabs: tabs, tab: tab, isSync: true })
90
+ })
86
91
  })
87
92
  var shiftedBy = thisTabs.getBoundingClientRect().y - initialY
88
93
  if (shiftedBy && (shiftedBy = Math.round(shiftedBy))) window.scrollBy({ top: shiftedBy, behavior: 'instant' })
89
94
  }
90
95
 
96
+ function querySelectorWithSiblings (scope, selector, siblingClass) {
97
+ var el = scope.querySelector(selector)
98
+ if (!el) return []
99
+ var result = [el]
100
+ while ((el = el.nextElementSibling) && el.classList.contains(siblingClass)) result.push(el)
101
+ return result
102
+ }
103
+
104
+ function toggleClassOnEach (elements, className, method) {
105
+ forEach.call(elements, function (el) {
106
+ el.classList[method](className)
107
+ })
108
+ }
109
+
91
110
  function toggleHidden (el, state) {
92
- el.classList.toggle('is-hidden', (el.hidden = state))
111
+ el.classList[(el.hidden = state) ? 'add' : 'remove']('is-hidden')
93
112
  }
94
113
 
95
114
  function toggleSelected (el, state) {
96
115
  el.setAttribute('aria-selected', '' + state)
97
- el.classList.toggle('is-selected', state)
116
+ el.classList[state ? 'add' : 'remove']('is-selected')
98
117
  el.tabIndex = state ? 0 : -1
99
118
  }
100
119
 
@@ -18,10 +18,10 @@ module Asciidoctor
18
18
  end
19
19
  tabs_number = doc.counter 'tabs-number'
20
20
  tabs_id = attrs['id'] || (generate_id %(tabs #{tabs_number}), doc)
21
- tabs_sync = !(block.option? 'nosync') && ((block.option? 'sync') || (doc.option? 'tabs-sync')) ? ' is-sync' : ''
22
- tabs_role = (tabs_role = attrs['role']) ? %( #{tabs_role}) : ''
23
- tabs = create_open_block parent, nil, { 'id' => tabs_id, 'role' => %(tabs#{tabs_sync}#{tabs_role} is-loading) }
24
- tabs.title = attrs['title']
21
+ tabs_role = 'tabs' + (!(block.option? 'nosync') && ((block.option? 'sync') || (doc.option? 'tabs-sync')) ?
22
+ ((gid = attrs['sync-group-id']) ? %( is-sync data-sync-group-id=#{gid.gsub ' ', ?\u00a0}) : ' is-sync') : '')
23
+ tabs_role += (tabs_user_role = attrs['role']) ? %( #{tabs_user_role} is-loading) : ' is-loading'
24
+ (tabs = create_open_block parent, nil, { 'id' => tabs_id, 'role' => tabs_role }).title = attrs['title']
25
25
  tablist = create_list parent, :ulist, { 'role' => 'tablist' }
26
26
  panes = {}
27
27
  set_id_on_tab = (doc.backend == 'html5') || (list_item_supports_id? doc)
@@ -35,11 +35,19 @@ module Asciidoctor
35
35
  JAVASCRIPT_FILE = ::File.join DATA_DIR, 'js/tabs.js'
36
36
 
37
37
  def process doc
38
+ if doc.attr? 'tabs-sync-storage-key'
39
+ config_attrs = %( data-sync-storage-key="#{doc.attr 'tabs-sync-storage-key'}")
40
+ if doc.attr? 'tabs-sync-storage-scope'
41
+ config_attrs += %( data-sync-storage-scope="#{doc.attr 'tabs-sync-storage-scope'}")
42
+ end
43
+ else
44
+ config_attrs = ''
45
+ end
38
46
  if doc.attr? 'linkcss'
39
47
  src = doc.normalize_web_path 'asciidoctor-tabs.js', (doc.attr 'scriptsdir')
40
- %(<script src="#{src}"></script>)
48
+ %(<script src="#{src}"#{config_attrs}></script>)
41
49
  elsif (script = doc.read_asset JAVASCRIPT_FILE)
42
- %(<script>\n#{script.chomp}\n</script>)
50
+ %(<script#{config_attrs}>\n#{script.chomp}\n</script>)
43
51
  end
44
52
  end
45
53
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Asciidoctor
4
4
  module Tabs
5
- VERSION = '1.0.0.beta.2'
5
+ VERSION = '1.0.0.beta.4'
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: asciidoctor-tabs
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0.beta.2
4
+ version: 1.0.0.beta.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dan Allen
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-01-30 00:00:00.000000000 Z
11
+ date: 2023-05-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: asciidoctor