shelving 1.209.0 → 1.210.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.
Files changed (70) hide show
  1. package/markup/rule/link.js +3 -3
  2. package/package.json +2 -2
  3. package/ui/block/Card.d.ts +1 -1
  4. package/ui/block/Card.js +5 -5
  5. package/ui/block/Card.tsx +5 -6
  6. package/ui/docs/DirectoryPage.js +1 -4
  7. package/ui/docs/DirectoryPage.tsx +1 -5
  8. package/ui/form/ArrayInput.d.ts +1 -1
  9. package/ui/form/ArrayInput.js +2 -2
  10. package/ui/form/ArrayInput.tsx +4 -3
  11. package/ui/form/Button.d.ts +1 -1
  12. package/ui/form/Button.js +4 -11
  13. package/ui/form/Button.tsx +3 -14
  14. package/ui/form/ButtonInput.d.ts +1 -1
  15. package/ui/form/ButtonInput.js +5 -15
  16. package/ui/form/ButtonInput.tsx +7 -24
  17. package/ui/form/CheckboxInput.js +2 -2
  18. package/ui/form/CheckboxInput.tsx +2 -2
  19. package/ui/form/Clickable.d.ts +9 -2
  20. package/ui/form/Clickable.js +12 -7
  21. package/ui/form/Clickable.tsx +18 -14
  22. package/ui/form/DictionaryInput.d.ts +1 -1
  23. package/ui/form/DictionaryInput.js +2 -2
  24. package/ui/form/DictionaryInput.tsx +4 -3
  25. package/ui/form/Input.d.ts +8 -8
  26. package/ui/form/Input.js +10 -23
  27. package/ui/form/Input.tsx +10 -34
  28. package/ui/form/OutputInput.d.ts +7 -0
  29. package/ui/form/OutputInput.js +8 -0
  30. package/ui/form/OutputInput.tsx +17 -0
  31. package/ui/form/RadioInput.js +2 -2
  32. package/ui/form/RadioInput.tsx +2 -2
  33. package/ui/form/index.d.ts +1 -0
  34. package/ui/form/index.js +1 -0
  35. package/ui/form/index.ts +1 -0
  36. package/ui/inline/Link.js +3 -2
  37. package/ui/inline/Link.tsx +2 -2
  38. package/ui/menu/MenuItem.d.ts +4 -6
  39. package/ui/menu/MenuItem.js +8 -9
  40. package/ui/menu/MenuItem.tsx +11 -14
  41. package/ui/misc/Color.d.ts +2 -2
  42. package/ui/misc/Color.tsx +2 -2
  43. package/ui/misc/Markup.d.ts +3 -2
  44. package/ui/misc/Markup.js +6 -2
  45. package/ui/misc/Markup.tsx +7 -3
  46. package/ui/misc/Status.d.ts +2 -2
  47. package/ui/misc/Status.tsx +2 -2
  48. package/ui/misc/Tag.d.ts +2 -3
  49. package/ui/misc/Tag.js +8 -5
  50. package/ui/misc/Tag.tsx +11 -12
  51. package/ui/page/HTML.js +1 -1
  52. package/ui/page/HTML.tsx +1 -1
  53. package/ui/page/Head.js +17 -22
  54. package/ui/page/Head.tsx +14 -20
  55. package/ui/router/Navigation.js +2 -2
  56. package/ui/router/Navigation.tsx +2 -2
  57. package/ui/router/Router.js +1 -1
  58. package/ui/router/Router.tsx +1 -1
  59. package/ui/util/css.d.ts +13 -9
  60. package/ui/util/css.js +4 -12
  61. package/ui/util/css.ts +21 -20
  62. package/ui/util/meta.d.ts +37 -12
  63. package/ui/util/meta.js +52 -3
  64. package/ui/util/meta.ts +101 -15
  65. package/util/link.d.ts +6 -6
  66. package/util/link.js +17 -16
  67. package/util/transform.d.ts +4 -2
  68. package/util/transform.js +6 -1
  69. package/util/url.d.ts +3 -3
  70. package/util/url.js +11 -7
@@ -8,9 +8,9 @@ import { createMarkupRule } from "../util/rule.js";
8
8
  /** Render `<a href="">` if the link is a valid one, or `<a>` (with no `href`) if it isn't. */
9
9
  function renderLinkMarkupRule({ groups: { title, href: unsafeHref } }, options, key) {
10
10
  const { url, root, schemes = HTTP_SCHEMES, rel } = options;
11
- const resolved = getLink(unsafeHref, url, root);
12
- const href = resolved && schemes.some(s => resolved.startsWith(s)) ? resolved : undefined;
13
- const children = title ? renderMarkup(title, options, "link") : resolved ? formatURI(resolved) : "";
11
+ const link = getLink(unsafeHref, url, root);
12
+ const href = link && schemes.includes(link.protocol) ? link?.href : undefined;
13
+ const children = title ? renderMarkup(title, options, "link") : link ? formatURI(link) : "";
14
14
  return {
15
15
  key,
16
16
  $$typeof: REACT_ELEMENT_TYPE,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "shelving",
3
- "version": "1.209.0",
3
+ "version": "1.210.0",
4
4
  "author": "Dave Houlbrooke <dave@shax.com>",
5
5
  "repository": {
6
6
  "type": "git",
@@ -76,7 +76,7 @@
76
76
  "build": "bun run --sequential build:*",
77
77
  "build:0:setup": "rm -rf ./dist && mkdir -p ./dist",
78
78
  "build:1:copy": "cp package.json dist/package.json && cp LICENSE.md dist/LICENSE.md && cp README.md dist/README.md && cp .npmignore dist/.npmignore && cp -r modules/ui dist/ui",
79
- "build:2:emit": "tsgo",
79
+ "build:2:emit": "tsgo -p tsconfig.build.json",
80
80
  "build:3:syntax": "bun run ./dist/index.js",
81
81
  "build:4:unit": "bun test ./dist/**/*.test.js --concurrent --only-failures --bail"
82
82
  },
@@ -15,4 +15,4 @@ export interface CardProps extends ClickableProps {
15
15
  * @example <Card><Heading>Static</Heading></Card>
16
16
  * @example <Card href="/foo" title="Open foo"><Heading>Clickable</Heading></Card>
17
17
  */
18
- export declare function Card({ children, disabled, href, onClick, title, target, download, ...variants }: CardProps): ReactElement;
18
+ export declare function Card({ children, href, onClick, title, ...props }: CardProps): ReactElement;
package/ui/block/Card.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { getClickable } from "../form/Clickable.js";
2
+ import { Clickable } from "../form/Clickable.js";
3
3
  import { getModuleClass } from "../util/css.js";
4
- import styles from "./Card.module.css";
4
+ import CARD_CSS from "./Card.module.css";
5
5
  /**
6
6
  * Cards are boxed areas for content to sit within — rendered as `<article>` since each card represents a self-contained piece of content.
7
7
  * - When `href` or `onClick` is set the card becomes navigable: a stretched overlay `<a>` / `<button>` covers the entire card while the children render normally inside.
@@ -10,7 +10,7 @@ import styles from "./Card.module.css";
10
10
  * @example <Card><Heading>Static</Heading></Card>
11
11
  * @example <Card href="/foo" title="Open foo"><Heading>Clickable</Heading></Card>
12
12
  */
13
- export function Card({ children, disabled, href, onClick, title, target, download, ...variants }) {
14
- const overlay = href || onClick ? getClickable({ disabled, href, onClick, title, target, download, children: title ?? "Open" }, styles.overlay) : null;
15
- return (_jsxs("article", { className: getModuleClass(styles, "card", variants), children: [overlay, overlay ? _jsx("div", { children: children }) : children] }));
13
+ export function Card({ children, href, onClick, title = "Open", ...props }) {
14
+ const overlay = (href || onClick) && _jsx(Clickable, { title: title, href: href, onClick: onClick, ...props, className: CARD_CSS.overlay });
15
+ return (_jsxs("article", { className: getModuleClass(CARD_CSS, "card", props), children: [overlay, overlay ? _jsx("div", { children: children }) : children] }));
16
16
  }
package/ui/block/Card.tsx CHANGED
@@ -1,7 +1,7 @@
1
1
  import type { ReactElement, ReactNode } from "react";
2
- import { type ClickableProps, getClickable } from "../form/Clickable.js";
2
+ import { Clickable, type ClickableProps } from "../form/Clickable.js";
3
3
  import { getModuleClass } from "../util/css.js";
4
- import styles from "./Card.module.css";
4
+ import CARD_CSS from "./Card.module.css";
5
5
 
6
6
  export interface CardProps extends ClickableProps {
7
7
  children?: ReactNode;
@@ -21,11 +21,10 @@ export interface CardProps extends ClickableProps {
21
21
  * @example <Card><Heading>Static</Heading></Card>
22
22
  * @example <Card href="/foo" title="Open foo"><Heading>Clickable</Heading></Card>
23
23
  */
24
- export function Card({ children, disabled, href, onClick, title, target, download, ...variants }: CardProps): ReactElement {
25
- const overlay =
26
- href || onClick ? getClickable({ disabled, href, onClick, title, target, download, children: title ?? "Open" }, styles.overlay) : null;
24
+ export function Card({ children, href, onClick, title = "Open", ...props }: CardProps): ReactElement {
25
+ const overlay = (href || onClick) && <Clickable title={title} href={href} onClick={onClick} {...props} className={CARD_CSS.overlay} />;
27
26
  return (
28
- <article className={getModuleClass(styles, "card", variants)}>
27
+ <article className={getModuleClass(CARD_CSS, "card", props)}>
29
28
  {overlay}
30
29
  {overlay ? <div>{children}</div> : children}
31
30
  </article>
@@ -1,12 +1,9 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { Prose } from "../block/Prose.js";
3
3
  import { Markup } from "../misc/Markup.js";
4
- import { requireMeta } from "../misc/MetaContext.js";
5
4
  import { Page } from "../page/Page.js";
6
5
  import { TreeCards } from "../tree/TreeCards.js";
7
6
  /** Page renderer for a `tree-directory` element — shows title, content, and child cards. */
8
7
  export function DirectoryPage({ title, name, description, content, children }) {
9
- const { url } = requireMeta();
10
- const path = (url?.pathname ?? "/");
11
- return (_jsxs(Page, { title: title ?? name, description: description, children: [content && (_jsx(Prose, { children: _jsx(Markup, { children: content }) })), _jsx(TreeCards, { path: path, children: children })] }));
8
+ return (_jsxs(Page, { title: title ?? name, description: description, children: [content && (_jsx(Prose, { children: _jsx(Markup, { children: content }) })), _jsx(TreeCards, { children: children })] }));
12
9
  }
@@ -1,16 +1,12 @@
1
1
  import type { ReactNode } from "react";
2
2
  import type { DirectoryElementProps } from "../../util/element.js";
3
- import type { AbsolutePath } from "../../util/path.js";
4
3
  import { Prose } from "../block/Prose.js";
5
4
  import { Markup } from "../misc/Markup.js";
6
- import { requireMeta } from "../misc/MetaContext.js";
7
5
  import { Page } from "../page/Page.js";
8
6
  import { TreeCards } from "../tree/TreeCards.js";
9
7
 
10
8
  /** Page renderer for a `tree-directory` element — shows title, content, and child cards. */
11
9
  export function DirectoryPage({ title, name, description, content, children }: DirectoryElementProps): ReactNode {
12
- const { url } = requireMeta();
13
- const path = (url?.pathname ?? "/") as AbsolutePath;
14
10
  return (
15
11
  <Page title={title ?? name} description={description}>
16
12
  {content && (
@@ -18,7 +14,7 @@ export function DirectoryPage({ title, name, description, content, children }: D
18
14
  <Markup>{content}</Markup>
19
15
  </Prose>
20
16
  )}
21
- <TreeCards path={path}>{children}</TreeCards>
17
+ <TreeCards>{children}</TreeCards>
22
18
  </Page>
23
19
  );
24
20
  }
@@ -1,7 +1,7 @@
1
1
  import type { ReactElement } from "react";
2
2
  import type { Schema } from "../../schema/Schema.js";
3
3
  import { type ImmutableArray } from "../../util/array.js";
4
- import { type ValueInputProps } from "./Input.js";
4
+ import type { ValueInputProps } from "./Input.js";
5
5
  export interface ArrayInputProps<T> extends ValueInputProps<ImmutableArray<T>> {
6
6
  one?: string;
7
7
  many?: string;
@@ -5,7 +5,7 @@ import { splitMessage } from "../../util/error.js";
5
5
  import { formatUnit } from "../../util/format.js";
6
6
  import { Flex } from "../block/Flex.js";
7
7
  import { Button } from "./Button.js";
8
- import { Input } from "./Input.js";
8
+ import { ButtonInput } from "./ButtonInput.js";
9
9
  import { SchemaInput } from "./SchemaInput.js";
10
10
  export function ArrayInput({ name,
11
11
  // title,
@@ -17,7 +17,7 @@ placeholder, required = false, disabled = false, message = "", value = [], onVal
17
17
  return (_jsxs(Flex, { column: true, children: [length ? (value.map((v, i) => {
18
18
  const k = i.toString();
19
19
  return (_jsxs(Flex, { children: [_jsx(SchemaInput, { name: k, schema: items, value: v, onValue: x => onValue(withArrayIndex(value, i, x)), message: messages[k], disabled: disabled }), _jsx(Button, { onClick: () => onValue(omitArrayIndex(value, i)), disabled: disableRemove, title: disableRemove ? `Minimum ${formatUnit(min, one, { unitDisplay: "long", one, many })}` : `Remove ${one}`, children: _jsx(XMarkIcon, {}) })] }, k));
20
- })) : (_jsx(Input, { onClick: addNewItem, name: name, required: required && min > 1, disabled: disabled, children: placeholder })), _jsxs(Flex, { children: [_jsxs(Button //
20
+ })) : (_jsx(ButtonInput, { onClick: addNewItem, name: name, required: required && min > 1, disabled: disabled, children: placeholder })), _jsxs(Flex, { children: [_jsxs(Button //
21
21
  , { onClick: addNewItem, disabled: disabled || (typeof max === "number" && length >= max), small: true, children: [_jsx(PlusIcon, {}), " Add ", one] }), length && min < 1 ? (_jsxs(Button //
22
22
  , { onClick: () => onValue([]), small: true, children: [_jsx(XMarkIcon, {}), " Clear ", many] })) : null] })] }));
23
23
  }
@@ -6,7 +6,8 @@ import { splitMessage } from "../../util/error.js";
6
6
  import { formatUnit } from "../../util/format.js";
7
7
  import { Flex } from "../block/Flex.js";
8
8
  import { Button } from "./Button.js";
9
- import { Input, type ValueInputProps } from "./Input.js";
9
+ import { ButtonInput } from "./ButtonInput.js";
10
+ import type { ValueInputProps } from "./Input.js";
10
11
  import { SchemaInput } from "./SchemaInput.js";
11
12
 
12
13
  export interface ArrayInputProps<T> extends ValueInputProps<ImmutableArray<T>> {
@@ -64,9 +65,9 @@ export function ArrayInput({
64
65
  );
65
66
  })
66
67
  ) : (
67
- <Input onClick={addNewItem} name={name} required={required && min > 1} disabled={disabled}>
68
+ <ButtonInput onClick={addNewItem} name={name} required={required && min > 1} disabled={disabled}>
68
69
  {placeholder}
69
- </Input>
70
+ </ButtonInput>
70
71
  )}
71
72
  <Flex>
72
73
  <Button //
@@ -19,7 +19,7 @@ export interface ButtonVariants extends FlexVariants, StatusVariants, ColorVaria
19
19
  interface ButtonProps extends ButtonVariants, ClickableProps {
20
20
  }
21
21
  /** Return either a `<button>` or an `<a href="">` styled as an button, based on whether an `onClick` or `href` prop is provided. */
22
- export declare function Button({ disabled, href, onClick, title, target, download, children, ...variants }: ButtonProps): ReactElement;
22
+ export declare function Button(props: ButtonProps): ReactElement;
23
23
  /** Get the full className for a button. */
24
24
  export declare function getButtonClass(variants: ButtonVariants): string;
25
25
  export {};
package/ui/form/Button.js CHANGED
@@ -1,20 +1,13 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
1
2
  import { FLEX_CSS } from "../block/Flex.js";
2
3
  import { getColorClass } from "../misc/Color.js";
3
4
  import { getStatusClass } from "../misc/Status.js";
4
5
  import { getClass, getModuleClass } from "../util/css.js";
5
6
  import BUTTON_CSS from "./Button.module.css";
6
- import { getClickable } from "./Clickable.js";
7
+ import { Clickable } from "./Clickable.js";
7
8
  /** Return either a `<button>` or an `<a href="">` styled as an button, based on whether an `onClick` or `href` prop is provided. */
8
- export function Button({ disabled, href, onClick, title, target, download, children, ...variants }) {
9
- return getClickable({
10
- disabled,
11
- href,
12
- onClick,
13
- title,
14
- target,
15
- download,
16
- children,
17
- }, getButtonClass(variants));
9
+ export function Button(props) {
10
+ return _jsx(Clickable, { ...props, className: getButtonClass(props) });
18
11
  }
19
12
  /** Get the full className for a button. */
20
13
  export function getButtonClass(variants) {
@@ -4,7 +4,7 @@ import { type ColorVariants, getColorClass } from "../misc/Color.js";
4
4
  import { getStatusClass, type StatusVariants } from "../misc/Status.js";
5
5
  import { type Classes, getClass, getModuleClass } from "../util/css.js";
6
6
  import BUTTON_CSS from "./Button.module.css";
7
- import { type ClickableProps, getClickable } from "./Clickable.js";
7
+ import { Clickable, type ClickableProps } from "./Clickable.js";
8
8
 
9
9
  /** Variants for buttons. */
10
10
  export interface ButtonVariants extends FlexVariants, StatusVariants, ColorVariants {
@@ -23,19 +23,8 @@ export interface ButtonVariants extends FlexVariants, StatusVariants, ColorVaria
23
23
  interface ButtonProps extends ButtonVariants, ClickableProps {}
24
24
 
25
25
  /** Return either a `<button>` or an `<a href="">` styled as an button, based on whether an `onClick` or `href` prop is provided. */
26
- export function Button({ disabled, href, onClick, title, target, download, children, ...variants }: ButtonProps): ReactElement {
27
- return getClickable(
28
- {
29
- disabled,
30
- href,
31
- onClick,
32
- title,
33
- target,
34
- download,
35
- children,
36
- },
37
- getButtonClass(variants),
38
- );
26
+ export function Button(props: ButtonProps): ReactElement {
27
+ return <Clickable {...props} className={getButtonClass(props)} />;
39
28
  }
40
29
 
41
30
  /** Get the full className for a button. */
@@ -4,4 +4,4 @@ import { type InputProps } from "./Input.js";
4
4
  export interface ButtonInputProps extends InputProps, ClickableProps {
5
5
  }
6
6
  /** Return either a `<button>` or an `<a href="">` styled as an input, based on whether an `onClick` or `href` prop is provided. */
7
- export declare function ButtonInput({ title, placeholder, disabled, href, onClick, target, download, children, }: ButtonInputProps): ReactElement;
7
+ export declare function ButtonInput({ title, placeholder, children, ...props }: ButtonInputProps): ReactElement;
@@ -1,19 +1,9 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
1
2
  import { notNullish } from "../../util/null.js";
2
- import { getClickable } from "./Clickable.js";
3
- import { ELEMENTS_BUTTON_INPUT_CLASS, PLACEHOLDER_ELEMENTS_BUTTON_INPUT_CLASS } from "./Input.js";
3
+ import { Clickable } from "./Clickable.js";
4
+ import { FLEX_BUTTON_INPUT_CLASS, PLACEHOLDER_ELEMENTS_BUTTON_INPUT_CLASS } from "./Input.js";
4
5
  /** Return either a `<button>` or an `<a href="">` styled as an input, based on whether an `onClick` or `href` prop is provided. */
5
- export function ButtonInput({
6
- // name,
7
- title, placeholder, disabled, href, onClick, target,
8
- // message = "",
9
- download, children = title, }) {
6
+ export function ButtonInput({ title, placeholder, children = title, ...props }) {
10
7
  const hasChildren = notNullish(children);
11
- return getClickable({
12
- disabled,
13
- href,
14
- onClick,
15
- target,
16
- download,
17
- children: hasChildren ? children : placeholder,
18
- }, hasChildren ? ELEMENTS_BUTTON_INPUT_CLASS : PLACEHOLDER_ELEMENTS_BUTTON_INPUT_CLASS);
8
+ return (_jsx(Clickable, { ...props, className: hasChildren ? FLEX_BUTTON_INPUT_CLASS : PLACEHOLDER_ELEMENTS_BUTTON_INPUT_CLASS, children: hasChildren ? children : placeholder }));
19
9
  }
@@ -1,33 +1,16 @@
1
1
  import type { ReactElement } from "react";
2
2
  import { notNullish } from "../../util/null.js";
3
- import { type ClickableProps, getClickable } from "./Clickable.js";
4
- import { ELEMENTS_BUTTON_INPUT_CLASS, type InputProps, PLACEHOLDER_ELEMENTS_BUTTON_INPUT_CLASS } from "./Input.js";
3
+ import { Clickable, type ClickableProps } from "./Clickable.js";
4
+ import { FLEX_BUTTON_INPUT_CLASS, type InputProps, PLACEHOLDER_ELEMENTS_BUTTON_INPUT_CLASS } from "./Input.js";
5
5
 
6
6
  export interface ButtonInputProps extends InputProps, ClickableProps {}
7
7
 
8
8
  /** Return either a `<button>` or an `<a href="">` styled as an input, based on whether an `onClick` or `href` prop is provided. */
9
- export function ButtonInput({
10
- // name,
11
- title,
12
- placeholder,
13
- disabled,
14
- href,
15
- onClick,
16
- target,
17
- // message = "",
18
- download,
19
- children = title,
20
- }: ButtonInputProps): ReactElement {
9
+ export function ButtonInput({ title, placeholder, children = title, ...props }: ButtonInputProps): ReactElement {
21
10
  const hasChildren = notNullish(children);
22
- return getClickable(
23
- {
24
- disabled,
25
- href,
26
- onClick,
27
- target,
28
- download,
29
- children: hasChildren ? children : placeholder,
30
- },
31
- hasChildren ? ELEMENTS_BUTTON_INPUT_CLASS : PLACEHOLDER_ELEMENTS_BUTTON_INPUT_CLASS,
11
+ return (
12
+ <Clickable {...props} className={hasChildren ? FLEX_BUTTON_INPUT_CLASS : PLACEHOLDER_ELEMENTS_BUTTON_INPUT_CLASS}>
13
+ {hasChildren ? children : placeholder}
14
+ </Clickable>
32
15
  );
33
16
  }
@@ -1,8 +1,8 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { notNullish } from "../../util/null.js";
3
- import { CHECKBOX_CLASS, ELEMENTS_LABEL_INPUT_CLASS } from "./Input.js";
3
+ import { CHECKBOX_CLASS, FLEX_LABEL_INPUT_CLASS } from "./Input.js";
4
4
  /** Checkbox element. */
5
5
  export function CheckboxInput({ name, title, placeholder = title || "Yes", required = false, disabled = false, message = "", value = false, onValue, children, }) {
6
6
  const hasChildren = notNullish(children);
7
- return (_jsxs("label", { className: ELEMENTS_LABEL_INPUT_CLASS, "aria-invalid": !!message, children: [_jsx("input", { name: name, type: "checkbox", defaultChecked: !!value, onChange: e => onValue?.(!!e.currentTarget.checked), required: required, disabled: disabled, title: message, className: CHECKBOX_CLASS }), _jsx("span", { children: hasChildren ? children : placeholder })] }));
7
+ return (_jsxs("label", { className: FLEX_LABEL_INPUT_CLASS, "aria-invalid": !!message, children: [_jsx("input", { name: name, type: "checkbox", defaultChecked: !!value, onChange: e => onValue?.(!!e.currentTarget.checked), required: required, disabled: disabled, title: message, className: CHECKBOX_CLASS }), _jsx("span", { children: hasChildren ? children : placeholder })] }));
8
8
  }
@@ -1,6 +1,6 @@
1
1
  import type { ReactElement, ReactNode } from "react";
2
2
  import { notNullish } from "../../util/null.js";
3
- import { CHECKBOX_CLASS, ELEMENTS_LABEL_INPUT_CLASS, type ValueInputProps } from "./Input.js";
3
+ import { CHECKBOX_CLASS, FLEX_LABEL_INPUT_CLASS, type ValueInputProps } from "./Input.js";
4
4
 
5
5
  export interface CheckboxProps extends ValueInputProps<boolean> {
6
6
  children?: ReactNode | undefined;
@@ -20,7 +20,7 @@ export function CheckboxInput({
20
20
  }: CheckboxProps): ReactElement {
21
21
  const hasChildren = notNullish(children);
22
22
  return (
23
- <label className={ELEMENTS_LABEL_INPUT_CLASS} aria-invalid={!!message}>
23
+ <label className={FLEX_LABEL_INPUT_CLASS} aria-invalid={!!message}>
24
24
  <input
25
25
  name={name}
26
26
  type="checkbox"
@@ -1,6 +1,6 @@
1
1
  import type { MouseEvent, ReactElement, ReactNode } from "react";
2
2
  import type { Path } from "../../util/path.js";
3
- import { type ImmutableURI, type URIString } from "../../util/uri.js";
3
+ import type { ImmutableURI, URIString } from "../../util/uri.js";
4
4
  /***
5
5
  * Handler for a clickable `onClick` event.
6
6
  * - Returned value (if defined) is notified to the user using `notifySuccess()`
@@ -24,5 +24,12 @@ export interface ClickableProps {
24
24
  /** The contents of the clickable. */
25
25
  children?: ReactNode | undefined;
26
26
  }
27
+ export interface StylableClickableProps extends ClickableProps {
28
+ className?: string | undefined;
29
+ }
27
30
  /** Return either a `<button>` or an `<a href="">` based on whether an `onClick` or `href` prop is provided. */
28
- export declare function getClickable(props: ClickableProps, className?: string): ReactElement;
31
+ export declare function Clickable(props: StylableClickableProps): ReactElement;
32
+ /** Return an `<a href="">` element. */
33
+ export declare function LinkClickable({ disabled, href, title, target, download, children, className, }: StylableClickableProps): ReactElement;
34
+ /** Return a `<button>` element. */
35
+ export declare function ButtonClickable({ disabled, onClick, title, children, className }: StylableClickableProps): ReactElement;
@@ -2,20 +2,25 @@ import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { useInstance } from "../../react/useInstance.js";
3
3
  import { useStore } from "../../react/useStore.js";
4
4
  import { BusyStore } from "../../store/BusyStore.js";
5
- import { isURI } from "../../util/uri.js";
5
+ import { isURLActive } from "../../util/index.js";
6
+ import { getLink } from "../../util/link.js";
6
7
  import { LOADING } from "../misc/Loading.js";
8
+ import { requireMeta } from "../misc/MetaContext.js";
7
9
  import { callNotifiedElement } from "../util/notice.js";
8
10
  /** Return either a `<button>` or an `<a href="">` based on whether an `onClick` or `href` prop is provided. */
9
- export function getClickable(props, className) {
10
- return props.href ? _jsx(LinkClickable, { ...props, className: className }) : _jsx(ButtonClickable, { ...props, className: className });
11
+ export function Clickable(props) {
12
+ return props.href ? _jsx(LinkClickable, { ...props }) : _jsx(ButtonClickable, { ...props });
11
13
  }
12
14
  /** Return an `<a href="">` element. */
13
- function LinkClickable({ disabled = false, href, title, target, download, children = "Go", className, }) {
14
- const link = disabled ? undefined : isURI(href) ? href.href : href;
15
- return (_jsx("a", { href: link, title: title, download: download, target: target, className: className, children: children }));
15
+ export function LinkClickable({ disabled = false, href, title, target, download, children = "Go", className, }) {
16
+ // Resolve `href` against the current page URL and site root so site-absolute paths (`/foo`) honour the base subfolder.
17
+ const { url, root } = requireMeta();
18
+ const link = disabled ? undefined : getLink(href, url, root);
19
+ const active = isURLActive(link, url);
20
+ return (_jsx("a", { href: link?.href, title: title, download: download, target: target, className: className, "aria-current": active ? "page" : undefined, children: children }));
16
21
  }
17
22
  /** Return a `<button>` element. */
18
- function ButtonClickable({ disabled = false, onClick, title, children = "Click", className, }) {
23
+ export function ButtonClickable({ disabled = false, onClick, title, children = "Click", className }) {
19
24
  // Create a `BusyStore<undefined>` to keep track of the `onClick` call and any thrown errors.
20
25
  const store = useInstance(BusyStore, undefined);
21
26
  // Track the `busy/unbusy` state of the button to show a loading spinner appropriately.
@@ -2,9 +2,12 @@ import type { MouseEvent, ReactElement, ReactNode } from "react";
2
2
  import { useInstance } from "../../react/useInstance.js";
3
3
  import { useStore } from "../../react/useStore.js";
4
4
  import { BusyStore } from "../../store/BusyStore.js";
5
+ import { isURLActive } from "../../util/index.js";
6
+ import { getLink } from "../../util/link.js";
5
7
  import type { Path } from "../../util/path.js";
6
- import { type ImmutableURI, isURI, type URIString } from "../../util/uri.js";
8
+ import type { ImmutableURI, URIString } from "../../util/uri.js";
7
9
  import { LOADING } from "../misc/Loading.js";
10
+ import { requireMeta } from "../misc/MetaContext.js";
8
11
  import { callNotifiedElement } from "../util/notice.js";
9
12
 
10
13
  /***
@@ -32,13 +35,17 @@ export interface ClickableProps {
32
35
  children?: ReactNode | undefined;
33
36
  }
34
37
 
38
+ export interface StylableClickableProps extends ClickableProps {
39
+ className?: string | undefined;
40
+ }
41
+
35
42
  /** Return either a `<button>` or an `<a href="">` based on whether an `onClick` or `href` prop is provided. */
36
- export function getClickable(props: ClickableProps, className?: string): ReactElement {
37
- return props.href ? <LinkClickable {...props} className={className} /> : <ButtonClickable {...props} className={className} />;
43
+ export function Clickable(props: StylableClickableProps): ReactElement {
44
+ return props.href ? <LinkClickable {...props} /> : <ButtonClickable {...props} />;
38
45
  }
39
46
 
40
47
  /** Return an `<a href="">` element. */
41
- function LinkClickable({
48
+ export function LinkClickable({
42
49
  disabled = false,
43
50
  href,
44
51
  title,
@@ -46,23 +53,20 @@ function LinkClickable({
46
53
  download,
47
54
  children = "Go",
48
55
  className,
49
- }: ClickableProps & { className: string | undefined }): ReactElement {
50
- const link: string | undefined = disabled ? undefined : isURI(href) ? href.href : href;
56
+ }: StylableClickableProps): ReactElement {
57
+ // Resolve `href` against the current page URL and site root so site-absolute paths (`/foo`) honour the base subfolder.
58
+ const { url, root } = requireMeta();
59
+ const link = disabled ? undefined : getLink(href, url, root);
60
+ const active = isURLActive(link, url);
51
61
  return (
52
- <a href={link} title={title} download={download} target={target} className={className}>
62
+ <a href={link?.href} title={title} download={download} target={target} className={className} aria-current={active ? "page" : undefined}>
53
63
  {children}
54
64
  </a>
55
65
  );
56
66
  }
57
67
 
58
68
  /** Return a `<button>` element. */
59
- function ButtonClickable({
60
- disabled = false,
61
- onClick,
62
- title,
63
- children = "Click",
64
- className,
65
- }: ClickableProps & { className: string | undefined }): ReactElement {
69
+ export function ButtonClickable({ disabled = false, onClick, title, children = "Click", className }: StylableClickableProps): ReactElement {
66
70
  // Create a `BusyStore<undefined>` to keep track of the `onClick` call and any thrown errors.
67
71
  const store = useInstance(BusyStore, undefined);
68
72
 
@@ -1,7 +1,7 @@
1
1
  import type { ReactElement } from "react";
2
2
  import type { Schema } from "../../schema/Schema.js";
3
3
  import type { ImmutableDictionary } from "../../util/dictionary.js";
4
- import { type ValueInputProps } from "./Input.js";
4
+ import type { ValueInputProps } from "./Input.js";
5
5
  export interface DictionaryInputProps<T> extends ValueInputProps<ImmutableDictionary<T>> {
6
6
  one?: string;
7
7
  many?: string;
@@ -6,7 +6,7 @@ import { formatUnit } from "../../util/format.js";
6
6
  import { omitProp } from "../../util/object.js";
7
7
  import { Flex } from "../block/Flex.js";
8
8
  import { Button } from "./Button.js";
9
- import { Input } from "./Input.js";
9
+ import { ButtonInput } from "./ButtonInput.js";
10
10
  import { SchemaInput } from "./SchemaInput.js";
11
11
  import { TextInput } from "./TextInput.js";
12
12
  export function DictionaryInput({ name,
@@ -26,7 +26,7 @@ placeholder, required = false, disabled = false, message = "", value = {}, onVal
26
26
  onValue(withDictionaryItem(omitDictionaryItem(value, k), x, v));
27
27
  k = x; // Update this so it works until content is re-rendered.
28
28
  }, message: message, disabled: disabled }), _jsx(SchemaInput, { name: `${r}-value`, schema: items, value: v, onValue: x => onValue(withDictionaryItem(value, k, x)), message: message, disabled: disabled }), _jsx(Button, { onClick: () => onValue(omitProp(value, k)), disabled: disableRemove, title: disableRemove ? `Minimum ${formatUnit(min, one, { unitDisplay: "long", one, many })}` : `Remove ${one}`, children: _jsx(XMarkIcon, {}) })] }, r));
29
- })) : (_jsx(Input, { onClick: addNewItem, name: name, required: required && min > 1, disabled: disabled, children: placeholder })), _jsxs(Flex, { children: [_jsxs(Button //
29
+ })) : (_jsx(ButtonInput, { onClick: addNewItem, name: name, required: required && min > 1, disabled: disabled, children: placeholder })), _jsxs(Flex, { children: [_jsxs(Button //
30
30
  , { onClick: addNewItem, disabled: disabled || (typeof max === "number" && length >= max), small: true, children: [_jsx(PlusIcon, {}), " Add ", one] }), length && min < 1 ? (_jsxs(Button //
31
31
  , { onClick: () => onValue({}), small: true, children: [_jsx(XMarkIcon, {}), " Clear ", many] })) : null] })] }));
32
32
  }
@@ -8,7 +8,8 @@ import { formatUnit } from "../../util/format.js";
8
8
  import { omitProp } from "../../util/object.js";
9
9
  import { Flex } from "../block/Flex.js";
10
10
  import { Button } from "./Button.js";
11
- import { Input, type ValueInputProps } from "./Input.js";
11
+ import { ButtonInput } from "./ButtonInput.js";
12
+ import type { ValueInputProps } from "./Input.js";
12
13
  import { SchemaInput } from "./SchemaInput.js";
13
14
  import { TextInput } from "./TextInput.js";
14
15
 
@@ -80,9 +81,9 @@ export function DictionaryInput({
80
81
  );
81
82
  })
82
83
  ) : (
83
- <Input onClick={addNewItem} name={name} required={required && min > 1} disabled={disabled}>
84
+ <ButtonInput onClick={addNewItem} name={name} required={required && min > 1} disabled={disabled}>
84
85
  {placeholder}
85
- </Input>
86
+ </ButtonInput>
86
87
  )}
87
88
  <Flex>
88
89
  <Button //
@@ -1,5 +1,4 @@
1
1
  import type { ReactElement, ReactNode } from "react";
2
- import { type ClickableProps } from "./Clickable.js";
3
2
  export declare const RADIO_CLASS: any;
4
3
  export declare const CHECKBOX_CLASS: any;
5
4
  export declare const PLACEHOLDER_CLASS: any;
@@ -9,11 +8,11 @@ export declare const MULTILINE_TEXT_INPUT_CLASS: string;
9
8
  export declare const SELECT_INPUT_CLASS: string;
10
9
  export declare const EMPTY_OPTION_INPUT_CLASS: any;
11
10
  export declare const VALUE_OPTION_INPUT_CLASS: any;
12
- export declare const ELEMENTS_INPUT_CLASS: string;
13
- export declare const ELEMENTS_BUTTON_INPUT_CLASS: string;
11
+ export declare const FLEX_INPUT_CLASS: string;
12
+ export declare const FLEX_BUTTON_INPUT_CLASS: string;
14
13
  export declare const PLACEHOLDER_ELEMENTS_BUTTON_INPUT_CLASS: string;
15
- export declare const ELEMENTS_LABEL_INPUT_CLASS: string;
16
- export declare const PLACEHOLDER_ELEMENTS_LABEL_INPUT_CLASS: string;
14
+ export declare const FLEX_LABEL_INPUT_CLASS: string;
15
+ export declare const PLACEHOLDER_FLEX_LABEL_INPUT_CLASS: string;
17
16
  export declare const WRAPPER_INPUT_CLASS: string;
18
17
  /** Props all inputs allow. */
19
18
  export interface InputProps {
@@ -37,11 +36,12 @@ export interface ValueInputProps<O, I = never> extends InputProps {
37
36
  /** Called when the value for the input changes, so you can make changes based on the new value (or `undefined` to set back to default). */
38
37
  onValue(value: O | undefined): void;
39
38
  }
40
- /** Return either a `<button>` or an `<a href="">` styled as an input, based on whether an `onClick` or `href` prop is provided. */
41
- export declare function Input({ title, placeholder, disabled, href, onClick, target, download, children, }: InputProps & ClickableProps): ReactElement;
42
39
  /** Input that is loading. */
43
40
  export declare const LOADING_INPUT: import("react/jsx-runtime").JSX.Element;
44
- /** Wraps an input with support for absolutely-positioned `data-slot` icon elements on either side. */
41
+ /**
42
+ * Wraps an input with support for absolutely-positioned `data-slot` icon elements on either side.
43
+ * - This is so you can put an icon before or after an input.
44
+ */
45
45
  export declare function InputWrapper({ children }: {
46
46
  children: ReactNode;
47
47
  }): ReactElement;
package/ui/form/Input.js CHANGED
@@ -2,7 +2,6 @@ import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { FLEX_CSS } from "../block/Flex.js";
3
3
  import { LOADING } from "../misc/Loading.js";
4
4
  import { getClass } from "../util/css.js";
5
- import { getClickable } from "./Clickable.js";
6
5
  import INPUT_CSS from "./Input.module.css";
7
6
  // Precomposed classes.
8
7
  export const RADIO_CLASS = INPUT_CSS.radio;
@@ -14,30 +13,18 @@ export const MULTILINE_TEXT_INPUT_CLASS = getClass(TEXT_INPUT_CLASS, INPUT_CSS.m
14
13
  export const SELECT_INPUT_CLASS = getClass(INPUT_CSS.input, INPUT_CSS.select);
15
14
  export const EMPTY_OPTION_INPUT_CLASS = INPUT_CSS.empty;
16
15
  export const VALUE_OPTION_INPUT_CLASS = INPUT_CSS.value;
17
- export const ELEMENTS_INPUT_CLASS = getClass(INPUT_CSS.input, FLEX_CSS.elements, FLEX_CSS.center);
18
- export const ELEMENTS_BUTTON_INPUT_CLASS = getClass(INPUT_CSS.input, INPUT_CSS.button, FLEX_CSS.elements, FLEX_CSS.center);
19
- export const PLACEHOLDER_ELEMENTS_BUTTON_INPUT_CLASS = getClass(ELEMENTS_BUTTON_INPUT_CLASS, INPUT_CSS.placeholder);
20
- export const ELEMENTS_LABEL_INPUT_CLASS = getClass(INPUT_CSS.input, INPUT_CSS.label, FLEX_CSS.elements, FLEX_CSS.left);
21
- export const PLACEHOLDER_ELEMENTS_LABEL_INPUT_CLASS = getClass(ELEMENTS_LABEL_INPUT_CLASS, INPUT_CSS.placeholder);
16
+ export const FLEX_INPUT_CLASS = getClass(INPUT_CSS.input, FLEX_CSS.elements, FLEX_CSS.center);
17
+ export const FLEX_BUTTON_INPUT_CLASS = getClass(INPUT_CSS.input, INPUT_CSS.button, FLEX_CSS.elements, FLEX_CSS.center);
18
+ export const PLACEHOLDER_ELEMENTS_BUTTON_INPUT_CLASS = getClass(FLEX_BUTTON_INPUT_CLASS, INPUT_CSS.placeholder);
19
+ export const FLEX_LABEL_INPUT_CLASS = getClass(INPUT_CSS.input, INPUT_CSS.label, FLEX_CSS.elements, FLEX_CSS.left);
20
+ export const PLACEHOLDER_FLEX_LABEL_INPUT_CLASS = getClass(FLEX_LABEL_INPUT_CLASS, INPUT_CSS.placeholder);
22
21
  export const WRAPPER_INPUT_CLASS = getClass(INPUT_CSS.input, INPUT_CSS.wrapper);
23
- /** Return either a `<button>` or an `<a href="">` styled as an input, based on whether an `onClick` or `href` prop is provided. */
24
- export function Input({
25
- // name,
26
- title, placeholder = title,
27
- // message,
28
- disabled, href, onClick, target, download, children, }) {
29
- return getClickable({
30
- disabled,
31
- href,
32
- onClick,
33
- target,
34
- download,
35
- children: children || placeholder,
36
- }, getClass(INPUT_CSS.input, INPUT_CSS.button, children ? undefined : INPUT_CSS.placeholder));
37
- }
38
22
  /** Input that is loading. */
39
- export const LOADING_INPUT = _jsx("div", { className: ELEMENTS_INPUT_CLASS, children: LOADING });
40
- /** Wraps an input with support for absolutely-positioned `data-slot` icon elements on either side. */
23
+ export const LOADING_INPUT = _jsx("div", { className: FLEX_INPUT_CLASS, children: LOADING });
24
+ /**
25
+ * Wraps an input with support for absolutely-positioned `data-slot` icon elements on either side.
26
+ * - This is so you can put an icon before or after an input.
27
+ */
41
28
  export function InputWrapper({ children }) {
42
29
  return _jsx("div", { className: WRAPPER_INPUT_CLASS, children: children });
43
30
  }