@neovici/cosmoz-tabs 5.2.0 → 5.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@neovici/cosmoz-tabs",
3
- "version": "5.2.0",
3
+ "version": "5.4.0",
4
4
  "description": "A multi views container element that allow navigation between the views using tabs or an accordion.",
5
5
  "keywords": [
6
6
  "web-components"
@@ -15,21 +15,19 @@
15
15
  },
16
16
  "license": "Apache-2.0",
17
17
  "author": "Neovici Development <dev@neovici.se>",
18
- "main": "cosmoz-tabs.js",
18
+ "main": "src/index.js",
19
19
  "directories": {
20
20
  "test": "test"
21
21
  },
22
22
  "files": [
23
- "cosmoz-*.js",
24
- "lib/**/*.js"
23
+ "src/**/*.js"
25
24
  ],
26
25
  "scripts": {
27
26
  "lint": "eslint --cache --ext .js .",
28
27
  "lint-tsc": "tsc",
29
- "start": "npm run storybook",
28
+ "start": "wds",
30
29
  "test": "wtr --coverage",
31
30
  "test:watch": "wtr --watch",
32
- "storybook": "start-storybook --node-resolve --watch --open",
33
31
  "storybook:build": "build-storybook",
34
32
  "storybook:deploy": "storybook-to-ghpages",
35
33
  "prepare": "husky install"
@@ -53,8 +51,17 @@
53
51
  "@commitlint/config-conventional"
54
52
  ]
55
53
  },
54
+ "exports": {
55
+ ".": "./src/index.js",
56
+ "./cosmoz-tabs": "./src/cosmoz-tabs.js",
57
+ "./cosmoz-tab": "./src/cosmoz-tab.js",
58
+ "./cosmoz-tab-card": "./src/cosmoz-tab-card.js",
59
+ "./cosmoz-tab-card.js": "./src/cosmoz-tab-card.js",
60
+ "./next/*": "./src/next/*"
61
+ },
56
62
  "dependencies": {
57
- "@neovici/cosmoz-utils": "^3.21.0",
63
+ "@neovici/cosmoz-page-router": "^8.0.0",
64
+ "@neovici/cosmoz-utils": "^3.25.0",
58
65
  "@polymer/iron-icon": "^3.0.0",
59
66
  "@polymer/iron-icons": "^3.0.0",
60
67
  "compute-scroll-into-view": "^1.0.17",
@@ -62,21 +69,19 @@
62
69
  "lit-html": "^1.4.0"
63
70
  },
64
71
  "devDependencies": {
65
- "@commitlint/cli": "^13.0.0",
66
- "@commitlint/config-conventional": "^13.0.0",
67
- "@neovici/eslint-config": "^1.3.0",
68
- "@open-wc/demoing-storybook": "^2.4.0",
72
+ "@commitlint/cli": "^17.0.0",
73
+ "@commitlint/config-conventional": "^17.0.0",
74
+ "@neovici/cfg": "^1.11.0",
69
75
  "@open-wc/testing": "^2.5.0",
70
76
  "@polymer/iron-list": "^3.1.0",
71
77
  "@semantic-release/changelog": "^6.0.0",
72
78
  "@semantic-release/git": "^10.0.0",
73
79
  "@storybook/storybook-deployer": "^2.8.0",
80
+ "@web/dev-server-storybook": "^0.5.0",
74
81
  "@web/test-runner": "^0.13.18",
75
- "@web/test-runner-selenium": "^0.5.2",
76
- "eslint": "^7.32.0",
77
- "husky": "^7.0.0",
78
- "semantic-release": "^18.0.0",
79
- "sinon": "^11.1.0",
82
+ "husky": "^8.0.0",
83
+ "semantic-release": "^19.0.0",
84
+ "sinon": "^14.0.0",
80
85
  "typescript": "^4.4.0"
81
86
  }
82
87
  }
@@ -60,7 +60,7 @@ const CosmozTabCard = ({ heading }) => html`
60
60
  </style>
61
61
 
62
62
  <div id="header" part="header">
63
- <h1 class="heading">${ heading }<slot name="after-title"></slot></h1>
63
+ <h1 class="heading" part="heading">${ heading }<slot name="after-title"></slot></h1>
64
64
  <slot name="card-actions"></slot>
65
65
  </div>
66
66
 
@@ -4,7 +4,7 @@ import {
4
4
  html,
5
5
  component
6
6
  } from 'haunted';
7
- import { useTab } from './lib/use-tab';
7
+ import { useTab } from './use-tab';
8
8
 
9
9
  /**
10
10
 
@@ -1,7 +1,7 @@
1
1
  // @license Copyright (C) 2015 Neovici AB - Apache 2 License
2
2
  import { html, component } from 'haunted';
3
- import { useTabs } from './lib/use-tabs';
4
- import { style, renderTab } from './lib/render';
3
+ import { useTabs } from './use-tabs';
4
+ import { style, renderTab } from './render';
5
5
  import './cosmoz-tab.js';
6
6
  import '@polymer/iron-icon';
7
7
  import '@polymer/iron-icons';
package/src/index.js ADDED
@@ -0,0 +1 @@
1
+ import './cosmoz-tabs';
@@ -0,0 +1,68 @@
1
+ import { tagged as css } from '@neovici/cosmoz-utils';
2
+
3
+ export default css`
4
+ :host {
5
+ position: relative;
6
+ display: flex;
7
+ box-sizing: border-box;
8
+ align-items: center;
9
+ justify-content: center;
10
+ flex: 1;
11
+ padding: 11px 24px;
12
+ color: inherit;
13
+ text-decoration: none;
14
+ text-align: center;
15
+ letter-spacing: 0.3px;
16
+ text-overflow: ellipsis;
17
+ white-space: nowrap;
18
+ cursor: pointer;
19
+ /* TODO(accessibility): focused tab should be outlined */
20
+ outline: 0;
21
+ }
22
+
23
+ :host([active]) {
24
+ color: var(--cosmoz-tabs-accent-color, #508aef);
25
+ box-shadow: inset 0 -3px 0px 0px var(--cosmoz-tabs-accent-color, #508aef);
26
+ font-weight: 700;
27
+ letter-spacing: 0;
28
+ }
29
+
30
+ :host([disabled]) {
31
+ opacity: 0.4;
32
+ pointer-events: none;
33
+ }
34
+
35
+ #iconSlot::slotted(*) {
36
+ flex-shrink: 0;
37
+ }
38
+
39
+ #contentSlot::slotted(*) {
40
+ flex: auto;
41
+ }
42
+
43
+ .badge {
44
+ font-family: var(--cosmoz-font-base, 'Verdana, Arial, sans-serif');
45
+ font-weight: normal;
46
+ font-size: 11px;
47
+ line-height: 1;
48
+ border-radius: 0.90909em;
49
+ box-sizing: border-box;
50
+
51
+ transform: translateY(-50%);
52
+ vertical-align: top;
53
+ min-width: 1.81818em;
54
+ padding: 0.40909em 0.36363em;
55
+
56
+ max-width: 80px;
57
+ text-overflow: ellipsis;
58
+ overflow: hidden;
59
+
60
+ background-color: var(--accent-color, #ff4081);
61
+ color: #ffffff;
62
+ text-align: center;
63
+ }
64
+
65
+ a {
66
+ display: contents;
67
+ }
68
+ `;
@@ -0,0 +1,53 @@
1
+ import { component, useEffect, useLayoutEffect } from 'haunted';
2
+ import { html, nothing } from 'lit-html';
3
+ import { ifDefined } from 'lit-html/directives/if-defined';
4
+ import computeScroll from 'compute-scroll-into-view';
5
+
6
+ import style from './cosmoz-tab.css';
7
+
8
+ const Tab = (host) => {
9
+ const { active, badge, href } = host;
10
+
11
+ useEffect(() => {
12
+ if (!host.getAttribute('tabindex')) {
13
+ host.setAttribute('tabindex', '-1');
14
+ }
15
+ host.setAttribute('role', 'tab');
16
+ }, []);
17
+
18
+ useLayoutEffect(() => {
19
+ const el = host;
20
+ el.toggleAttribute('aria-selected', !!active);
21
+
22
+ if (!active) {
23
+ return;
24
+ }
25
+ computeScroll(el, {
26
+ block: 'nearest',
27
+ inline: 'center',
28
+ boundary: el.parentElement,
29
+ }).forEach(({ el, top, left }) =>
30
+ el.scroll({ top, left, behavior: 'smooth' })
31
+ );
32
+ }, [active]);
33
+
34
+ return html`
35
+ <style>
36
+ ${style}
37
+ </style>
38
+ <a part="link" href=${ifDefined(href)}>
39
+ <slot id="iconSlot" name="icon"></slot>
40
+ <slot id="contentSlot"></slot>
41
+ ${badge
42
+ ? html`<span class="badge" part="badge">${badge}</span>`
43
+ : nothing}
44
+ </a>
45
+ `;
46
+ };
47
+
48
+ customElements.define(
49
+ 'cosmoz-tab-next',
50
+ component(Tab, {
51
+ observedAttributes: ['active', 'badge', 'href'],
52
+ })
53
+ );
@@ -0,0 +1,22 @@
1
+ import { tagged as css } from '@neovici/cosmoz-utils';
2
+
3
+ export default css`
4
+ :host {
5
+ background-color: var(--cosmoz-tabs-bg-color, #fff);
6
+ color: var(--cosmoz-tabs-text-color, #606c7e);
7
+ font-family: var(--cosmoz-tabs-font-family, inherit);
8
+ font-size: var(--cosmoz-tabs-font-size, 13px);
9
+ line-height: var(--cosmoz-tabs-line-height, 19px);
10
+ box-shadow: var(--cosmoz-tabs-shadow, inset 0 -1px 0 0 #e5e6eb);
11
+ flex: none;
12
+ display: flex;
13
+ align-items: center;
14
+ overflow-x: auto;
15
+ -webkit-overflow-scrolling: auto;
16
+ scrollbar-width: none;
17
+ padding-bottom: 1px;
18
+ }
19
+ :host::-webkit-scrollbar {
20
+ display: none;
21
+ }
22
+ `;
@@ -0,0 +1,17 @@
1
+ import { html, component, useEffect } from 'haunted';
2
+ import style from './cosmoz-tabs.css';
3
+
4
+ const Tabs = (host) => {
5
+ useEffect(() => {
6
+ host.setAttribute('role', 'tablist');
7
+ }, []);
8
+
9
+ return html`
10
+ <style>
11
+ ${style}
12
+ </style>
13
+ <slot></slot>
14
+ `;
15
+ };
16
+
17
+ customElements.define('cosmoz-tabs-next', component(Tabs));
@@ -0,0 +1,4 @@
1
+ import './cosmoz-tabs';
2
+ import './cosmoz-tab';
3
+
4
+ export * from './use-tabs';
@@ -0,0 +1,50 @@
1
+ /* eslint-disable import/group-exports */
2
+ import { html, useMemo, useCallback, useRef } from 'haunted';
3
+ import { useHashParam } from '@neovici/cosmoz-page-router/lib/use-hash-param';
4
+
5
+ const isValid = (tab) => !tab.hidden && !tab.disabled,
6
+ valid = (tabs) => tabs.find(isValid),
7
+ choose = (tabs, name) => {
8
+ const tab = name && tabs.find((tab) => tab.name === name);
9
+ return tab && isValid(tab) ? tab : valid(tabs);
10
+ };
11
+
12
+ export const useTabs = (tabs, { hashParam }) => {
13
+ const [name, activate] = useHashParam(hashParam),
14
+ ref = useRef([]),
15
+ active = useMemo(() => choose(tabs, name), [tabs, name]),
16
+ activated = useMemo(() => {
17
+ const name = active.name;
18
+ return (ref.current = [...ref.current.filter((i) => i !== name), name]);
19
+ }, [active]),
20
+ onActivate = useCallback(
21
+ (e) => {
22
+ if (e.button !== 0 || e.metaKey || e.ctrlKey) {
23
+ return;
24
+ }
25
+ const { name } = e.currentTarget;
26
+ activate(name);
27
+ },
28
+ [activate]
29
+ );
30
+
31
+ return {
32
+ tabs,
33
+ active,
34
+ activated,
35
+ activate,
36
+ onActivate,
37
+ };
38
+ };
39
+
40
+ export const renderTabs = ({ tabs, active, onActivate }) =>
41
+ tabs.map(
42
+ (tab) => html`<cosmoz-tab-next
43
+ name=${tab.name}
44
+ ?active=${active.name === tab.name}
45
+ ?hidden=${tab.hidden}
46
+ ?disabled=${tab.disabled}
47
+ @click=${onActivate}
48
+ >${tab.title}</cosmoz-tab-next
49
+ >`
50
+ );
File without changes
File without changes
@@ -1,6 +1,6 @@
1
1
  import { useState, useEffect, useMemo, useCallback } from 'haunted';
2
2
  import { notifyProperty } from '@neovici/cosmoz-utils/lib/hooks/use-notify-property';
3
- import { useHashParam, link } from './use-hash-param';
3
+ import { useHashParam, link } from '@neovici/cosmoz-page-router/lib/use-hash-param';
4
4
  import { choose, collect, getName, isValid } from './utils';
5
5
  import computeScroll from 'compute-scroll-into-view';
6
6
 
@@ -48,7 +48,7 @@ const useTabSelectedEffect = (host, selectedTab) => {
48
48
  selected, hashParam
49
49
  } = host,
50
50
  [tabs, setTabs] = useState([]),
51
- param = useHashParam(hashParam),
51
+ [param] = useHashParam(hashParam),
52
52
  selection = hashParam == null || param == null && selected != null ? selected : param,
53
53
  selectedTab = useMemo(() => choose(tabs, selection), [tabs, selection]);
54
54
 
@@ -89,9 +89,7 @@ const useTabSelectedEffect = (host, selectedTab) => {
89
89
 
90
90
  e.preventDefault();
91
91
  window.history.pushState({}, '', href(tab));
92
- // TODO: drop this when we drop iron-location/cosmoz-page-location
93
- requestAnimationFrame(() => window.dispatchEvent(new CustomEvent('location-changed')));
94
- requestAnimationFrame(() => window.dispatchEvent(new CustomEvent('hash-changed')));
92
+ requestAnimationFrame(() => window.dispatchEvent(new CustomEvent('hashchange')));
95
93
  }, []),
96
94
  href
97
95
  };
File without changes
@@ -1,40 +0,0 @@
1
- import {
2
- useState, useEffect, useMemo
3
- } from 'haunted';
4
-
5
- const hashUrl = () => new URL(location.hash.replace(/^#!?/iu, '').replace('%23', '#'), location.origin),
6
- parameterize = hashParam => hashParam ? () => new URLSearchParams(hashUrl().hash.replace('#', '')).get(hashParam) : undefined,
7
- link = (hashParam, value) => {
8
- if (!hashParam) {
9
- return;
10
- }
11
- const url = hashUrl(),
12
- sp = new URLSearchParams(url.hash.replace('#', ''));
13
- sp.set(hashParam, value);
14
- return '#!' + Object.assign(url, { hash: sp }).href.replace(location.origin, '');
15
- },
16
- useHashParam = hashParam => {
17
- const parameterized = useMemo(() => parameterize(hashParam), [hashParam]),
18
- [param, setParam] = useState(parameterized);
19
-
20
- useEffect(() => {
21
- if (parameterized == null) {
22
- return;
23
- }
24
- const readParam = () => requestAnimationFrame(() => setParam(parameterized));
25
- readParam();
26
- window.addEventListener('popstate', readParam);
27
- window.addEventListener('hash-changed', readParam);
28
- return () => {
29
- window.removeEventListener('popstate', readParam);
30
- window.removeEventListener('hash-changed', readParam);
31
- };
32
- }, [parameterized]);
33
-
34
- return param;
35
- };
36
-
37
- export {
38
- link,
39
- useHashParam
40
- };