@canonical/react-components 0.37.0 → 0.37.1

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.
@@ -1,7 +1,8 @@
1
1
  import { ReactNode, HTMLProps } from "react";
2
2
  import type { GenerateLink, NavItem, LogoProps } from "./types";
3
- import { PropsWithSpread, SubComponentProps, Theme } from "../../types";
3
+ import { PropsWithSpread, SubComponentProps } from "../../types";
4
4
  import { SearchBoxProps } from "../SearchBox";
5
+ import { Theme } from "../../enums";
5
6
  export declare type Props = PropsWithSpread<{
6
7
  /**
7
8
  * By default the header is constrained to the width of the grid. Use this
@@ -15,12 +15,12 @@ var _NavigationLink = _interopRequireDefault(require("./NavigationLink"));
15
15
 
16
16
  var _NavigationMenu = _interopRequireDefault(require("./NavigationMenu"));
17
17
 
18
- var _types = require("../../types");
19
-
20
18
  var _SearchBox = _interopRequireDefault(require("../SearchBox"));
21
19
 
22
20
  var _hooks = require("../../hooks");
23
21
 
22
+ var _enums = require("../../enums");
23
+
24
24
  var _excluded = ["url", "src", "title", "icon", "aria-current", "aria-label"],
25
25
  _excluded2 = ["fullWidth", "generateLink", "items", "itemsRight", "leftNavProps", "logo", "navProps", "rightNavProps", "searchProps", "theme"];
26
26
 
@@ -42,6 +42,12 @@ function _iterableToArrayLimit(arr, i) { var _i = arr == null ? null : typeof Sy
42
42
 
43
43
  function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; }
44
44
 
45
+ function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); enumerableOnly && (symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; })), keys.push.apply(keys, symbols); } return keys; }
46
+
47
+ function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = null != arguments[i] ? arguments[i] : {}; i % 2 ? ownKeys(Object(source), !0).forEach(function (key) { _defineProperty(target, key, source[key]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)) : ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } return target; }
48
+
49
+ function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
50
+
45
51
  function _extends() { _extends = Object.assign ? Object.assign.bind() : function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
46
52
 
47
53
  function _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }
@@ -91,12 +97,14 @@ var generateLogo = function generateLogo(logo, generateLink) {
91
97
  return /*#__PURE__*/_react.default.createElement("div", _extends({
92
98
  className: "p-navigation__tagged-logo"
93
99
  }, logoProps), /*#__PURE__*/_react.default.createElement(_NavigationLink.default, {
94
- className: "p-navigation__link",
95
- url: url,
96
- label: content,
97
- "aria-label": ariaLabel,
98
100
  generateLink: generateLink,
99
- isSelected: !!ariaCurrent
101
+ link: {
102
+ "aria-label": ariaLabel,
103
+ className: "p-navigation__link",
104
+ isSelected: !!ariaCurrent,
105
+ label: content,
106
+ url: url
107
+ }
100
108
  }));
101
109
  }
102
110
 
@@ -124,16 +132,18 @@ var generateItems = function generateItems(items, closeMobileMenu, generateLink)
124
132
  "is-selected": item.isSelected
125
133
  }),
126
134
  key: i
127
- }, /*#__PURE__*/_react.default.createElement(_NavigationLink.default, _extends({}, item, {
128
- onClick: function onClick(evt) {
129
- var _item$onClick;
130
-
131
- (_item$onClick = item.onClick) === null || _item$onClick === void 0 ? void 0 : _item$onClick.call(item, evt);
132
- closeMobileMenu();
133
- },
135
+ }, /*#__PURE__*/_react.default.createElement(_NavigationLink.default, {
134
136
  generateLink: generateLink,
135
- className: (0, _classnames.default)("p-navigation__link", item.className)
136
- })));
137
+ link: _objectSpread(_objectSpread({}, item), {}, {
138
+ className: (0, _classnames.default)("p-navigation__link", item.className),
139
+ onClick: function onClick(evt) {
140
+ var _item$onClick;
141
+
142
+ (_item$onClick = item.onClick) === null || _item$onClick === void 0 ? void 0 : _item$onClick.call(item, evt);
143
+ closeMobileMenu();
144
+ }
145
+ })
146
+ }));
137
147
  });
138
148
  };
139
149
 
@@ -200,8 +210,8 @@ var Navigation = function Navigation(_ref) {
200
210
  className: (0, _classnames.default)("p-navigation", headerProps.className, {
201
211
  "has-menu-open": mobileMenuOpen,
202
212
  "has-search-open": searchOpen,
203
- "is-dark": theme === _types.Theme.DARK,
204
- "is-light": theme === _types.Theme.LIGHT
213
+ "is-dark": theme === _enums.Theme.DARK,
214
+ "is-light": theme === _enums.Theme.LIGHT
205
215
  })
206
216
  }), /*#__PURE__*/_react.default.createElement("div", {
207
217
  className: fullWidth ? "p-navigation__row--full-width" : "p-navigation__row"
@@ -1,11 +1,10 @@
1
- import type { HTMLProps } from "react";
2
- import { PropsWithSpread } from "../../../types";
3
1
  import type { GenerateLink, NavLink } from "../types";
4
- declare type Props = PropsWithSpread<NavLink & {
2
+ declare type Props = {
5
3
  generateLink?: GenerateLink;
6
- }, HTMLProps<HTMLAnchorElement>>;
4
+ link: NavLink;
5
+ };
7
6
  /**
8
7
  * This component is used internally to display links inside the Navigation component.
9
8
  */
10
- declare const NavigationLink: ({ generateLink, isSelected, label, url, ...props }: Props) => JSX.Element;
9
+ declare const NavigationLink: ({ generateLink, link }: Props) => JSX.Element | null;
11
10
  export default NavigationLink;
@@ -5,9 +5,15 @@ Object.defineProperty(exports, "__esModule", {
5
5
  });
6
6
  exports.default = void 0;
7
7
 
8
+ var _propTypes = _interopRequireDefault(require("prop-types"));
9
+
8
10
  var _react = _interopRequireDefault(require("react"));
9
11
 
10
- var _excluded = ["generateLink", "isSelected", "label", "url"];
12
+ var _utils = require("../../../utils");
13
+
14
+ var _excluded = ["isSelected"],
15
+ _excluded2 = ["isSelected", "label", "url"],
16
+ _excluded3 = ["isSelected", "label", "url"];
11
17
 
12
18
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
13
19
 
@@ -28,29 +34,40 @@ function _objectWithoutPropertiesLoose(source, excluded) { if (source == null) r
28
34
  */
29
35
  var NavigationLink = function NavigationLink(_ref) {
30
36
  var generateLink = _ref.generateLink,
31
- isSelected = _ref.isSelected,
32
- label = _ref.label,
33
- url = _ref.url,
34
- props = _objectWithoutProperties(_ref, _excluded);
35
-
36
- var ariaCurrent = isSelected ? "page" : undefined;
37
+ link = _ref.link;
37
38
 
39
+ // const ariaCurrent = isSelected ? "page" : undefined;
38
40
  if (generateLink) {
39
- // If a function has been provided then use it to generate the link element.
41
+ var isSelected = link.isSelected,
42
+ linkProps = _objectWithoutProperties(link, _excluded); // If a function has been provided then use it to generate the link element.
43
+
44
+
40
45
  return /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, generateLink(_objectSpread({
41
46
  isSelected: isSelected,
42
- label: label,
43
- url: url,
44
- "aria-current": ariaCurrent
45
- }, props)));
46
- } else {
47
- // If a function has not been provided then use a standard anchor element.
48
- return /*#__PURE__*/_react.default.createElement("a", _extends({
49
- href: url
50
- }, props, {
51
- "aria-current": ariaCurrent
47
+ "aria-current": isSelected ? "page" : undefined
48
+ }, linkProps)));
49
+ } else if ((0, _utils.isNavigationAnchor)(link)) {
50
+ var _isSelected = link.isSelected,
51
+ label = link.label,
52
+ url = link.url,
53
+ _linkProps = _objectWithoutProperties(link, _excluded2);
54
+
55
+ return /*#__PURE__*/_react.default.createElement("a", _extends({}, _linkProps, {
56
+ href: url,
57
+ "aria-current": _isSelected ? "page" : undefined
52
58
  }), label);
59
+ } else if ((0, _utils.isNavigationButton)(link)) {
60
+ var _isSelected2 = link.isSelected,
61
+ _label = link.label,
62
+ _url = link.url,
63
+ _linkProps2 = _objectWithoutProperties(link, _excluded3);
64
+
65
+ return /*#__PURE__*/_react.default.createElement("button", _extends({}, _linkProps2, {
66
+ "aria-current": _isSelected2 ? "page" : undefined
67
+ }), _label);
53
68
  }
69
+
70
+ return null;
54
71
  };
55
72
 
56
73
  var _default = NavigationLink;
@@ -25,6 +25,12 @@ function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj &&
25
25
 
26
26
  function _extends() { _extends = Object.assign ? Object.assign.bind() : function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
27
27
 
28
+ function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); enumerableOnly && (symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; })), keys.push.apply(keys, symbols); } return keys; }
29
+
30
+ function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = null != arguments[i] ? arguments[i] : {}; i % 2 ? ownKeys(Object(source), !0).forEach(function (key) { _defineProperty(target, key, source[key]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)) : ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } return target; }
31
+
32
+ function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
33
+
28
34
  function _slicedToArray(arr, i) { return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(arr, i) || _nonIterableRest(); }
29
35
 
30
36
  function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
@@ -86,10 +92,12 @@ var NavigationMenu = function NavigationMenu(_ref) {
86
92
  }, items.map(function (item, i) {
87
93
  return /*#__PURE__*/_react.default.createElement("li", {
88
94
  key: i
89
- }, /*#__PURE__*/_react.default.createElement(_NavigationLink.default, _extends({}, item, {
95
+ }, /*#__PURE__*/_react.default.createElement(_NavigationLink.default, {
90
96
  generateLink: generateLink,
91
- className: (0, _classnames.default)("p-navigation__dropdown-item", item.className)
92
- })));
97
+ link: _objectSpread(_objectSpread({}, item), {}, {
98
+ className: (0, _classnames.default)("p-navigation__dropdown-item", item.className)
99
+ })
100
+ }));
93
101
  })));
94
102
  };
95
103
 
@@ -1,3 +1,3 @@
1
1
  export { default } from "./Navigation";
2
2
  export type { Props as NavigationProps } from "./Navigation";
3
- export type { GenerateLink, LogoProps, NavLink, NavMenu, NavItem, } from "./types";
3
+ export type { GenerateLink, LogoProps, NavLink, NavLinkAnchor, NavLinkBase, NavLinkButton, NavMenu, NavItem, } from "./types";
@@ -1,6 +1,6 @@
1
- import type { HTMLProps, ReactNode } from "react";
1
+ import type { HTMLAttributes, HTMLProps, ReactNode } from "react";
2
2
  import { PropsWithSpread } from "../../types";
3
- export declare type NavLink = PropsWithSpread<{
3
+ export declare type NavLinkBase = {
4
4
  /**
5
5
  * Whether this nav item is currently selected.
6
6
  */
@@ -13,7 +13,14 @@ export declare type NavLink = PropsWithSpread<{
13
13
  * The URL of the link.
14
14
  */
15
15
  url?: string;
16
- }, HTMLProps<HTMLAnchorElement>>;
16
+ };
17
+ export declare type NavLinkAnchor = PropsWithSpread<NavLinkBase & {
18
+ url: string;
19
+ }, HTMLAttributes<HTMLAnchorElement>>;
20
+ export declare type NavLinkButton = PropsWithSpread<NavLinkBase & {
21
+ url?: never;
22
+ }, HTMLAttributes<HTMLButtonElement>>;
23
+ export declare type NavLink = NavLinkAnchor | NavLinkButton;
17
24
  export declare type NavMenu = {
18
25
  /**
19
26
  * Whether to align the dropdown to the right edge of the navigation item.
@@ -0,0 +1,13 @@
1
+ /**
2
+ * The Vanilla theme types.
3
+ */
4
+ export declare enum Theme {
5
+ /**
6
+ * The dark Vanilla theme.
7
+ */
8
+ DARK = "dark",
9
+ /**
10
+ * The light Vanilla theme.
11
+ */
12
+ LIGHT = "light"
13
+ }
package/dist/enums.js ADDED
@@ -0,0 +1,17 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.Theme = void 0;
7
+
8
+ /**
9
+ * The Vanilla theme types.
10
+ */
11
+ var Theme;
12
+ exports.Theme = Theme;
13
+
14
+ (function (Theme) {
15
+ Theme["DARK"] = "dark";
16
+ Theme["LIGHT"] = "light";
17
+ })(Theme || (exports.Theme = Theme = {}));
package/dist/index.d.ts CHANGED
@@ -65,7 +65,7 @@ export type { ListProps } from "./components/List";
65
65
  export type { MainTableProps } from "./components/MainTable";
66
66
  export type { ModularTableProps } from "./components/ModularTable";
67
67
  export type { ModalProps } from "./components/Modal";
68
- export type { GenerateLink, LogoProps, NavigationProps, NavItem, NavLink, } from "./components/Navigation";
68
+ export type { GenerateLink, LogoProps, NavigationProps, NavItem, NavLink, NavLinkAnchor, NavLinkBase, NavLinkButton, } from "./components/Navigation";
69
69
  export type { NotificationProps } from "./components/Notification";
70
70
  export type { PaginationProps } from "./components/Pagination";
71
71
  export type { RadioInputProps } from "./components/RadioInput";
@@ -87,4 +87,6 @@ export type { TextareaProps } from "./components/Textarea";
87
87
  export type { TooltipProps } from "./components/Tooltip";
88
88
  export { useClickOutside, useId, useListener, useOnEscapePressed, usePrevious, useThrottle, useWindowFitment, } from "./hooks";
89
89
  export type { WindowFitment } from "./hooks";
90
- export type { ClassName, Headings, PropsWithSpread, SortDirection, SubComponentProps, Theme, TSFixMe, ValueOf, } from "./types";
90
+ export { isNavigationAnchor, isNavigationButton } from "./utils";
91
+ export type { ClassName, Headings, PropsWithSpread, SortDirection, SubComponentProps, TSFixMe, ValueOf, } from "./types";
92
+ export { Theme } from "./enums";
package/dist/index.js CHANGED
@@ -293,12 +293,30 @@ Object.defineProperty(exports, "Textarea", {
293
293
  return _Textarea.default;
294
294
  }
295
295
  });
296
+ Object.defineProperty(exports, "Theme", {
297
+ enumerable: true,
298
+ get: function get() {
299
+ return _enums.Theme;
300
+ }
301
+ });
296
302
  Object.defineProperty(exports, "Tooltip", {
297
303
  enumerable: true,
298
304
  get: function get() {
299
305
  return _Tooltip.default;
300
306
  }
301
307
  });
308
+ Object.defineProperty(exports, "isNavigationAnchor", {
309
+ enumerable: true,
310
+ get: function get() {
311
+ return _utils.isNavigationAnchor;
312
+ }
313
+ });
314
+ Object.defineProperty(exports, "isNavigationButton", {
315
+ enumerable: true,
316
+ get: function get() {
317
+ return _utils.isNavigationButton;
318
+ }
319
+ });
302
320
  Object.defineProperty(exports, "useClickOutside", {
303
321
  enumerable: true,
304
322
  get: function get() {
@@ -432,6 +450,10 @@ var _Tooltip = _interopRequireDefault(require("./components/Tooltip"));
432
450
 
433
451
  var _hooks = require("./hooks");
434
452
 
453
+ var _utils = require("./utils");
454
+
455
+ var _enums = require("./enums");
456
+
435
457
  function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function _getRequireWildcardCache(nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
436
458
 
437
459
  function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || _typeof(obj) !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
@@ -35,16 +35,3 @@ export declare type TSFixMe = any;
35
35
  * defined in EnumLike.
36
36
  */
37
37
  export declare type ValueOf<T> = T[keyof T];
38
- /**
39
- * The Vanilla theme types.
40
- */
41
- export declare enum Theme {
42
- /**
43
- * The dark Vanilla theme.
44
- */
45
- DARK = "dark",
46
- /**
47
- * The light Vanilla theme.
48
- */
49
- LIGHT = "light"
50
- }
@@ -2,52 +2,4 @@
2
2
 
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
- });
6
- exports.Theme = void 0;
7
-
8
- /**
9
- * This type should be used for all className props in order to ensure
10
- * consistency across components.
11
- */
12
-
13
- /**
14
- * The allowable heading levels.
15
- */
16
-
17
- /**
18
- * This type can be used when defining props that also spread the props of
19
- * something else. It ensures that the defined component props are never
20
- * overwritten by the spread props.
21
- */
22
-
23
- /**
24
- * The allowable sort directions e.g. for a sortable table.
25
- */
26
-
27
- /**
28
- * This type can be used when passing props to a sub component. It makes all
29
- * component props optional.
30
- */
31
-
32
- /**
33
- * This type is simply an alias for the 'any' type and should be used sparingly,
34
- * if at all.
35
- */
36
- // eslint-disable-line @typescript-eslint/no-explicit-any
37
-
38
- /**
39
- * This type allows for converting an object const into an enum-like construct,
40
- * e.g. value: ValueOf<typeof EnumLike> will only allow value to be a value
41
- * defined in EnumLike.
42
- */
43
-
44
- /**
45
- * The Vanilla theme types.
46
- */
47
- var Theme;
48
- exports.Theme = Theme;
49
-
50
- (function (Theme) {
51
- Theme["DARK"] = "dark";
52
- Theme["LIGHT"] = "light";
53
- })(Theme || (exports.Theme = Theme = {}));
5
+ });
package/dist/utils.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import { NavLink, NavLinkAnchor, NavLinkButton } from "./components/Navigation";
1
2
  export declare const IS_DEV: boolean;
2
3
  /**
3
4
  * Find substring and wrap in <strong /> tag
@@ -9,3 +10,13 @@ export declare const highlightSubString: (str?: string, subString?: string) => {
9
10
  text: string;
10
11
  match: boolean;
11
12
  };
13
+ /**
14
+ * Whether a navigation item is an anchor.
15
+ * @param link - The navigation item.
16
+ */
17
+ export declare const isNavigationAnchor: (link: NavLink) => link is NavLinkAnchor;
18
+ /**
19
+ * Whether a navigation item is a button.
20
+ * @param link - The navigation item.
21
+ */
22
+ export declare const isNavigationButton: (link: NavLink) => link is NavLinkButton;
package/dist/utils.js CHANGED
@@ -3,7 +3,7 @@
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
- exports.highlightSubString = exports.IS_DEV = void 0;
6
+ exports.isNavigationButton = exports.isNavigationAnchor = exports.highlightSubString = exports.IS_DEV = void 0;
7
7
  var IS_DEV = process.env.NODE_ENV === "development";
8
8
  /**
9
9
  * Find substring and wrap in <strong /> tag
@@ -31,5 +31,27 @@ var highlightSubString = function highlightSubString(str, subString) {
31
31
  match: newStr !== str
32
32
  };
33
33
  };
34
+ /**
35
+ * Whether a navigation item is an anchor.
36
+ * @param link - The navigation item.
37
+ */
38
+
39
+
40
+ exports.highlightSubString = highlightSubString;
41
+
42
+ var isNavigationAnchor = function isNavigationAnchor(link) {
43
+ return !!link.url;
44
+ };
45
+ /**
46
+ * Whether a navigation item is a button.
47
+ * @param link - The navigation item.
48
+ */
49
+
50
+
51
+ exports.isNavigationAnchor = isNavigationAnchor;
52
+
53
+ var isNavigationButton = function isNavigationButton(link) {
54
+ return !link.url;
55
+ };
34
56
 
35
- exports.highlightSubString = highlightSubString;
57
+ exports.isNavigationButton = isNavigationButton;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@canonical/react-components",
3
- "version": "0.37.0",
3
+ "version": "0.37.1",
4
4
  "main": "dist/index.js",
5
5
  "module": "dist/index.js",
6
6
  "author": "Huw Wilkins <huw.wilkins@canonical.com>",