@neumorphism-ui/elements 0.0.0-stage → 1.0.0-next.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.
Files changed (125) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +63 -2
  3. package/cdn/neu.min.js +205 -0
  4. package/custom-elements.json +1892 -0
  5. package/dist/all.d.ts +12 -0
  6. package/dist/all.js +12 -0
  7. package/dist/avatar.d.ts +3 -0
  8. package/dist/avatar.js +4 -0
  9. package/dist/badge.d.ts +3 -0
  10. package/dist/badge.js +4 -0
  11. package/dist/button-group.d.ts +3 -0
  12. package/dist/button-group.js +4 -0
  13. package/dist/button.d.ts +3 -0
  14. package/dist/button.js +4 -0
  15. package/dist/components/avatar/avatar.d.ts +38 -0
  16. package/dist/components/avatar/avatar.def.d.ts +24 -0
  17. package/dist/components/avatar/avatar.def.js +33 -0
  18. package/dist/components/avatar/avatar.js +58 -0
  19. package/dist/components/avatar/avatar.styles.d.ts +1 -0
  20. package/dist/components/avatar/avatar.styles.js +13 -0
  21. package/dist/components/badge/badge.d.ts +17 -0
  22. package/dist/components/badge/badge.def.d.ts +12 -0
  23. package/dist/components/badge/badge.def.js +29 -0
  24. package/dist/components/badge/badge.js +5 -0
  25. package/dist/components/badge/badge.styles.d.ts +1 -0
  26. package/dist/components/badge/badge.styles.js +10 -0
  27. package/dist/components/button/button.d.ts +25 -0
  28. package/dist/components/button/button.def.d.ts +28 -0
  29. package/dist/components/button/button.def.js +45 -0
  30. package/dist/components/button/button.js +87 -0
  31. package/dist/components/button/button.styles.d.ts +2 -0
  32. package/dist/components/button/button.styles.js +43 -0
  33. package/dist/components/button-group/button-group.d.ts +19 -0
  34. package/dist/components/button-group/button-group.def.d.ts +12 -0
  35. package/dist/components/button-group/button-group.def.js +25 -0
  36. package/dist/components/button-group/button-group.js +13 -0
  37. package/dist/components/button-group/button-group.styles.d.ts +7 -0
  38. package/dist/components/button-group/button-group.styles.js +19 -0
  39. package/dist/components/divider/divider.d.ts +14 -0
  40. package/dist/components/divider/divider.def.d.ts +8 -0
  41. package/dist/components/divider/divider.def.js +24 -0
  42. package/dist/components/divider/divider.js +13 -0
  43. package/dist/components/divider/divider.styles.d.ts +1 -0
  44. package/dist/components/divider/divider.styles.js +7 -0
  45. package/dist/components/icon/icon-drawer.d.ts +12 -0
  46. package/dist/components/icon/icon-drawer.js +38 -0
  47. package/dist/components/icon/icon-loader.d.ts +5 -0
  48. package/dist/components/icon/icon-loader.js +102 -0
  49. package/dist/components/icon/icon-registry.d.ts +11 -0
  50. package/dist/components/icon/icon-registry.js +26 -0
  51. package/dist/components/icon/icon.d.ts +25 -0
  52. package/dist/components/icon/icon.def.d.ts +15 -0
  53. package/dist/components/icon/icon.def.js +24 -0
  54. package/dist/components/icon/icon.js +23 -0
  55. package/dist/components/icon/icon.styles.d.ts +1 -0
  56. package/dist/components/icon/icon.styles.js +7 -0
  57. package/dist/components/icon/system-icons.d.ts +2 -0
  58. package/dist/components/icon/system-icons.js +17 -0
  59. package/dist/components/icon-button/icon-button.d.ts +51 -0
  60. package/dist/components/icon-button/icon-button.def.d.ts +40 -0
  61. package/dist/components/icon-button/icon-button.def.js +44 -0
  62. package/dist/components/icon-button/icon-button.js +21 -0
  63. package/dist/components/icon-button/icon-button.styles.d.ts +1 -0
  64. package/dist/components/icon-button/icon-button.styles.js +12 -0
  65. package/dist/components/spinner/spinner.d.ts +19 -0
  66. package/dist/components/spinner/spinner.def.d.ts +12 -0
  67. package/dist/components/spinner/spinner.def.js +25 -0
  68. package/dist/components/spinner/spinner.js +17 -0
  69. package/dist/components/spinner/spinner.styles.d.ts +1 -0
  70. package/dist/components/spinner/spinner.styles.js +13 -0
  71. package/dist/components/surface/surface.d.ts +33 -0
  72. package/dist/components/surface/surface.def.d.ts +26 -0
  73. package/dist/components/surface/surface.def.js +27 -0
  74. package/dist/components/surface/surface.js +5 -0
  75. package/dist/components/surface/surface.styles.d.ts +1 -0
  76. package/dist/components/surface/surface.styles.js +17 -0
  77. package/dist/components/switch/switch.d.ts +60 -0
  78. package/dist/components/switch/switch.def.d.ts +28 -0
  79. package/dist/components/switch/switch.def.js +33 -0
  80. package/dist/components/switch/switch.js +125 -0
  81. package/dist/components/switch/switch.styles.d.ts +1 -0
  82. package/dist/components/switch/switch.styles.js +29 -0
  83. package/dist/components/text-field/implicit-submit.d.ts +8 -0
  84. package/dist/components/text-field/implicit-submit.js +42 -0
  85. package/dist/components/text-field/text-field.d.ts +116 -0
  86. package/dist/components/text-field/text-field.def.d.ts +143 -0
  87. package/dist/components/text-field/text-field.def.js +63 -0
  88. package/dist/components/text-field/text-field.js +207 -0
  89. package/dist/components/text-field/text-field.styles.d.ts +1 -0
  90. package/dist/components/text-field/text-field.styles.js +39 -0
  91. package/dist/core/define.d.ts +33 -0
  92. package/dist/core/define.js +15 -0
  93. package/dist/core/dom.d.ts +21 -0
  94. package/dist/core/dom.js +51 -0
  95. package/dist/core/form-control.d.ts +35 -0
  96. package/dist/core/form-control.js +128 -0
  97. package/dist/core/html.d.ts +13 -0
  98. package/dist/core/html.js +35 -0
  99. package/dist/core/neu-element.d.ts +87 -0
  100. package/dist/core/neu-element.js +285 -0
  101. package/dist/core/props.d.ts +44 -0
  102. package/dist/core/props.js +52 -0
  103. package/dist/core/styles.d.ts +8 -0
  104. package/dist/core/styles.js +40 -0
  105. package/dist/core/validation.d.ts +4 -0
  106. package/dist/core/validation.js +26 -0
  107. package/dist/divider.d.ts +3 -0
  108. package/dist/divider.js +4 -0
  109. package/dist/icon-button.d.ts +3 -0
  110. package/dist/icon-button.js +4 -0
  111. package/dist/icon.d.ts +3 -0
  112. package/dist/icon.js +4 -0
  113. package/dist/icons.d.ts +2 -0
  114. package/dist/icons.js +1 -0
  115. package/dist/spinner.d.ts +3 -0
  116. package/dist/spinner.js +4 -0
  117. package/dist/ssr.d.ts +11 -0
  118. package/dist/ssr.js +41 -0
  119. package/dist/surface.d.ts +3 -0
  120. package/dist/surface.js +4 -0
  121. package/dist/switch.d.ts +3 -0
  122. package/dist/switch.js +4 -0
  123. package/dist/text-field.d.ts +3 -0
  124. package/dist/text-field.js +4 -0
  125. package/package.json +104 -3
package/dist/all.d.ts ADDED
@@ -0,0 +1,12 @@
1
+ export * from './avatar.js';
2
+ export * from './badge.js';
3
+ export * from './button.js';
4
+ export * from './button-group.js';
5
+ export * from './divider.js';
6
+ export * from './icon.js';
7
+ export * from './icon-button.js';
8
+ export * from './icons.js';
9
+ export * from './spinner.js';
10
+ export * from './surface.js';
11
+ export * from './switch.js';
12
+ export * from './text-field.js';
package/dist/all.js ADDED
@@ -0,0 +1,12 @@
1
+ export * from './avatar.js';
2
+ export * from './badge.js';
3
+ export * from './button.js';
4
+ export * from './button-group.js';
5
+ export * from './divider.js';
6
+ export * from './icon.js';
7
+ export * from './icon-button.js';
8
+ export * from './icons.js';
9
+ export * from './spinner.js';
10
+ export * from './surface.js';
11
+ export * from './switch.js';
12
+ export * from './text-field.js';
@@ -0,0 +1,3 @@
1
+ import { NeuAvatar } from './components/avatar/avatar.js';
2
+ export { NeuAvatar };
3
+ export { avatarDef } from './components/avatar/avatar.def.js';
package/dist/avatar.js ADDED
@@ -0,0 +1,4 @@
1
+ import { NeuAvatar } from './components/avatar/avatar.js';
2
+ NeuAvatar.define();
3
+ export { NeuAvatar };
4
+ export { avatarDef } from './components/avatar/avatar.def.js';
@@ -0,0 +1,3 @@
1
+ import { NeuBadge } from './components/badge/badge.js';
2
+ export { NeuBadge };
3
+ export { badgeDef } from './components/badge/badge.def.js';
package/dist/badge.js ADDED
@@ -0,0 +1,4 @@
1
+ import { NeuBadge } from './components/badge/badge.js';
2
+ NeuBadge.define();
3
+ export { NeuBadge };
4
+ export { badgeDef } from './components/badge/badge.def.js';
@@ -0,0 +1,3 @@
1
+ import { NeuButtonGroup } from './components/button-group/button-group.js';
2
+ export { NeuButtonGroup };
3
+ export { buttonGroupDef } from './components/button-group/button-group.def.js';
@@ -0,0 +1,4 @@
1
+ import { NeuButtonGroup } from './components/button-group/button-group.js';
2
+ NeuButtonGroup.define();
3
+ export { NeuButtonGroup };
4
+ export { buttonGroupDef } from './components/button-group/button-group.def.js';
@@ -0,0 +1,3 @@
1
+ import { NeuButton } from './components/button/button.js';
2
+ export { NeuButton };
3
+ export { buttonDef } from './components/button/button.def.js';
package/dist/button.js ADDED
@@ -0,0 +1,4 @@
1
+ import { NeuButton } from './components/button/button.js';
2
+ NeuButton.define();
3
+ export { NeuButton };
4
+ export { buttonDef } from './components/button/button.def.js';
@@ -0,0 +1,38 @@
1
+ import { NeuElement } from '../../core/neu-element.js';
2
+ export declare class NeuAvatar extends NeuElement {
3
+ static def: import("../../ssr.js").ComponentDef<{
4
+ readonly src: {
5
+ readonly type: "string";
6
+ readonly reflect: true;
7
+ };
8
+ readonly label: {
9
+ readonly type: "string";
10
+ };
11
+ readonly initials: {
12
+ readonly type: "string";
13
+ };
14
+ readonly size: {
15
+ readonly type: "string";
16
+ readonly default: "md";
17
+ readonly values: readonly ["sm", "md", "lg"];
18
+ readonly reflect: true;
19
+ };
20
+ readonly shape: {
21
+ readonly type: "string";
22
+ readonly default: "circle";
23
+ readonly values: readonly ["circle", "rounded"];
24
+ readonly reflect: true;
25
+ };
26
+ }>;
27
+ src: string;
28
+ label: string;
29
+ initials: string;
30
+ size: 'sm' | 'md' | 'lg';
31
+ shape: 'circle' | 'rounded';
32
+ private broken;
33
+ protected willUpdate(changed: ReadonlySet<string>): void;
34
+ protected rendered(): void;
35
+ protected update(changed: ReadonlySet<string>): void;
36
+ private showing;
37
+ private setBroken;
38
+ }
@@ -0,0 +1,24 @@
1
+ export declare const avatarDef: import("../../core/define.js").ComponentDef<{
2
+ readonly src: {
3
+ readonly type: "string";
4
+ readonly reflect: true;
5
+ };
6
+ readonly label: {
7
+ readonly type: "string";
8
+ };
9
+ readonly initials: {
10
+ readonly type: "string";
11
+ };
12
+ readonly size: {
13
+ readonly type: "string";
14
+ readonly default: "md";
15
+ readonly values: readonly ["sm", "md", "lg"];
16
+ readonly reflect: true;
17
+ };
18
+ readonly shape: {
19
+ readonly type: "string";
20
+ readonly default: "circle";
21
+ readonly values: readonly ["circle", "rounded"];
22
+ readonly reflect: true;
23
+ };
24
+ }>;
@@ -0,0 +1,33 @@
1
+ import { defineComponent } from '../../core/define.js';
2
+ import { html, unsafeHTML } from '../../core/html.js';
3
+ import { themeCssProperties } from '../../core/styles.js';
4
+ import { systemIconSvg } from '../icon/system-icons.js';
5
+ import { avatarStyles } from './avatar.styles.js';
6
+ export const avatarDef = defineComponent({
7
+ tag: 'neu-avatar',
8
+ props: {
9
+ src: { type: 'string', reflect: true },
10
+ label: { type: 'string' },
11
+ initials: { type: 'string' },
12
+ size: { type: 'string', default: 'md', values: ['sm', 'md', 'lg'], reflect: true },
13
+ shape: { type: 'string', default: 'circle', values: ['circle', 'rounded'], reflect: true },
14
+ },
15
+ styles: avatarStyles,
16
+ template: (p) => {
17
+ const show = p.src ? 'image' : p.initials ? 'initials' : 'icon';
18
+ return html `<span part="base" class="base" data-ref="base" data-show="${show}"><img part="image" class="image" data-ref="image" alt=""${p.src ? html ` src="${p.src}"` : ''}><span part="initials" class="initials" data-ref="initials">${p.initials}</span><span part="icon" class="icon">${unsafeHTML(systemIconSvg('user') ?? '')}</span></span>`;
19
+ },
20
+ docs: {
21
+ summary: 'A user picture that falls back to initials, then to a generic user icon. With `label` it is an image named by it; without, it is decorative and hidden from assistive technology.',
22
+ events: [],
23
+ slots: [],
24
+ parts: [
25
+ { name: 'base', description: 'The sized, raised wrapper.' },
26
+ { name: 'image', description: 'The picture.' },
27
+ { name: 'initials', description: 'The initials shown when there is no picture.' },
28
+ { name: 'icon', description: 'Wrapper around the fallback user icon.' },
29
+ { name: 'svg', description: 'The rendered fallback SVG element.' },
30
+ ],
31
+ cssProperties: themeCssProperties,
32
+ },
33
+ });
@@ -0,0 +1,58 @@
1
+ import { setText } from '../../core/dom.js';
2
+ import { NeuElement } from '../../core/neu-element.js';
3
+ import { avatarDef } from './avatar.def.js';
4
+ export class NeuAvatar extends NeuElement {
5
+ static def = avatarDef;
6
+ broken = false;
7
+ willUpdate(changed) {
8
+ if (changed.has('src'))
9
+ this.setBroken(false);
10
+ }
11
+ rendered() {
12
+ const img = this.ref('image');
13
+ if (!img)
14
+ return;
15
+ img.addEventListener('error', () => this.setBroken(true));
16
+ // The error event may have fired before this element upgraded; it will not fire again. An SVG
17
+ // without intrinsic size is not caught here: browsers give it a 300x150 (or viewBox) size.
18
+ if (img.hasAttribute('src') && img.complete && img.naturalWidth === 0)
19
+ this.broken = true;
20
+ this.toggleState('image-error', this.broken);
21
+ }
22
+ update(changed) {
23
+ if (changed.has('label')) {
24
+ // Like neu-icon: a picture is named by its label, otherwise it is decoration.
25
+ const label = String(this.props.label);
26
+ this.internals.role = label ? 'img' : null;
27
+ this.internals.ariaLabel = label || null;
28
+ this.internals.ariaHidden = label ? null : 'true';
29
+ }
30
+ if (changed.has('initials'))
31
+ setText(this.ref('initials'), String(this.props.initials));
32
+ const img = this.ref('image');
33
+ if (!img)
34
+ return;
35
+ const src = String(this.props.src);
36
+ if (changed.has('src') && (img.getAttribute('src') ?? '') !== src) {
37
+ if (src)
38
+ img.setAttribute('src', src);
39
+ else
40
+ img.removeAttribute('src');
41
+ }
42
+ const base = this.ref('base');
43
+ const show = this.showing();
44
+ if (base.dataset.show !== show)
45
+ base.dataset.show = show;
46
+ }
47
+ showing() {
48
+ return this.props.src && !this.broken ? 'image' : this.props.initials ? 'initials' : 'icon';
49
+ }
50
+ setBroken(broken) {
51
+ if (this.broken === broken)
52
+ return;
53
+ this.broken = broken;
54
+ this.toggleState('image-error', broken);
55
+ if (this.ref('base'))
56
+ this.update(new Set());
57
+ }
58
+ }
@@ -0,0 +1 @@
1
+ export declare const avatarStyles = "\n:host {\n --_neu-bg: var(--neu-bg, #e0e5ec);\n --_neu-accent-c: var(--neu-accent, #6d5dfc);\n --_neu-d: var(--neu-distance, 6px);\n --_neu-k: var(--neu-intensity, 1);\n --_neu-r: var(--neu-radius, 12px);\n --_neu-danger: var(--neu-danger, #e5484d);\n --_neu-dur: var(--neu-duration, 150ms);\n --_neu-sd: var(--neu-shadow-dark, color-mix(in oklab, var(--_neu-bg), black min(100%, calc(var(--_neu-shade, 18%) * var(--_neu-k)))));\n --_neu-sl: var(--neu-shadow-light, color-mix(in oklab, var(--_neu-bg), white min(100%, calc(var(--_neu-light, 100%) * var(--_neu-k)))));\n --_neu-fg: var(--neu-fg, color-mix(in oklab, var(--_neu-bg), var(--_neu-ink, black) var(--_neu-fg-mix, 32%)));\n --_neu-fg-muted: var(--neu-fg-muted, color-mix(in oklab, var(--_neu-bg), var(--_neu-ink, black) var(--_neu-fg-muted-mix, 18%)));\n --_neu-accent-soft: color-mix(in oklab, var(--_neu-accent-c) 25%, var(--_neu-bg));\n --_neu-focus: 0 0 0 3px color-mix(in oklab, var(--_neu-accent-c) 45%, transparent);\n --_neu-raised: var(--_neu-d) var(--_neu-d) calc(var(--_neu-d) * 2) var(--_neu-sd), calc(var(--_neu-d) * -1) calc(var(--_neu-d) * -1) calc(var(--_neu-d) * 2) var(--_neu-sl);\n --_neu-raised-sm: calc(var(--_neu-d) * 0.5) calc(var(--_neu-d) * 0.5) var(--_neu-d) var(--_neu-sd), calc(var(--_neu-d) * -0.5) calc(var(--_neu-d) * -0.5) var(--_neu-d) var(--_neu-sl);\n --_neu-inset: inset var(--_neu-d) var(--_neu-d) calc(var(--_neu-d) * 2) var(--_neu-sd), inset calc(var(--_neu-d) * -1) calc(var(--_neu-d) * -1) calc(var(--_neu-d) * 2) var(--_neu-sl);\n --_neu-inset-sm: inset calc(var(--_neu-d) * 0.5) calc(var(--_neu-d) * 0.5) var(--_neu-d) var(--_neu-sd), inset calc(var(--_neu-d) * -0.5) calc(var(--_neu-d) * -0.5) var(--_neu-d) var(--_neu-sl);\n font-family: var(--neu-font);\n color: var(--_neu-fg);\n -webkit-tap-highlight-color: transparent;\n}\n:host(:not(:defined)) { visibility: inherit !important; min-block-size: auto !important; }\n:host([hidden]) { display: none !important; }\n:host(:focus) { outline: none; }\n*, *::before, *::after { box-sizing: border-box; }\n\n:host { display: inline-block; vertical-align: middle; --_neu-s: 2.5rem; inline-size: var(--_neu-s); block-size: var(--_neu-s); }\n:host([size='sm']) { --_neu-s: 2rem; }\n:host([size='lg']) { --_neu-s: 3.5rem; }\n.base { display: flex; align-items: center; justify-content: center; box-sizing: border-box; inline-size: 100%; block-size: 100%; overflow: hidden; font-size: calc(var(--_neu-s) * 0.4); font-weight: 600; border-radius: 50%; box-shadow: var(--_neu-raised-sm); background: var(--_neu-bg); color: var(--_neu-fg-muted); }\n:host([shape='rounded']) .base { border-radius: var(--_neu-r); }\n.image, .initials, .icon { display: none; }\n.image { inline-size: 100%; block-size: 100%; object-fit: cover; }\n.base[data-show='image'] .image, .base[data-show='initials'] .initials { display: block; }\n.base[data-show='icon'] .icon { display: flex; inline-size: 60%; block-size: 60%; }\n.icon svg { inline-size: 100%; block-size: 100%; }\n";
@@ -0,0 +1,13 @@
1
+ import { hostTheme } from '../../core/styles.js';
2
+ export const avatarStyles = `${hostTheme}
3
+ :host { display: inline-block; vertical-align: middle; --_neu-s: 2.5rem; inline-size: var(--_neu-s); block-size: var(--_neu-s); }
4
+ :host([size='sm']) { --_neu-s: 2rem; }
5
+ :host([size='lg']) { --_neu-s: 3.5rem; }
6
+ .base { display: flex; align-items: center; justify-content: center; box-sizing: border-box; inline-size: 100%; block-size: 100%; overflow: hidden; font-size: calc(var(--_neu-s) * 0.4); font-weight: 600; border-radius: 50%; box-shadow: var(--_neu-raised-sm); background: var(--_neu-bg); color: var(--_neu-fg-muted); }
7
+ :host([shape='rounded']) .base { border-radius: var(--_neu-r); }
8
+ .image, .initials, .icon { display: none; }
9
+ .image { inline-size: 100%; block-size: 100%; object-fit: cover; }
10
+ .base[data-show='image'] .image, .base[data-show='initials'] .initials { display: block; }
11
+ .base[data-show='icon'] .icon { display: flex; inline-size: 60%; block-size: 60%; }
12
+ .icon svg { inline-size: 100%; block-size: 100%; }
13
+ `;
@@ -0,0 +1,17 @@
1
+ import { NeuElement } from '../../core/neu-element.js';
2
+ export declare class NeuBadge extends NeuElement {
3
+ static def: import("../../ssr.js").ComponentDef<{
4
+ readonly variant: {
5
+ readonly type: "string";
6
+ readonly default: "neutral";
7
+ readonly values: readonly ["neutral", "accent", "success", "warning", "danger"];
8
+ readonly reflect: true;
9
+ };
10
+ readonly pill: {
11
+ readonly type: "boolean";
12
+ readonly reflect: true;
13
+ };
14
+ }>;
15
+ variant: 'neutral' | 'accent' | 'success' | 'warning' | 'danger';
16
+ pill: boolean;
17
+ }
@@ -0,0 +1,12 @@
1
+ export declare const badgeDef: import("../../core/define.js").ComponentDef<{
2
+ readonly variant: {
3
+ readonly type: "string";
4
+ readonly default: "neutral";
5
+ readonly values: readonly ["neutral", "accent", "success", "warning", "danger"];
6
+ readonly reflect: true;
7
+ };
8
+ readonly pill: {
9
+ readonly type: "boolean";
10
+ readonly reflect: true;
11
+ };
12
+ }>;
@@ -0,0 +1,29 @@
1
+ import { defineComponent } from '../../core/define.js';
2
+ import { html } from '../../core/html.js';
3
+ import { themeCssProperties } from '../../core/styles.js';
4
+ import { badgeStyles } from './badge.styles.js';
5
+ export const badgeDef = defineComponent({
6
+ tag: 'neu-badge',
7
+ props: {
8
+ variant: {
9
+ type: 'string',
10
+ default: 'neutral',
11
+ values: ['neutral', 'accent', 'success', 'warning', 'danger'],
12
+ reflect: true,
13
+ },
14
+ pill: { type: 'boolean', reflect: true },
15
+ },
16
+ styles: badgeStyles,
17
+ template: () => html `<span part="base" class="base"><slot></slot></span>`,
18
+ docs: {
19
+ summary: 'A small raised label for status or counts.',
20
+ events: [],
21
+ slots: [{ name: '', description: 'The badge text.' }],
22
+ parts: [{ name: 'base', description: 'The badge itself.' }],
23
+ cssProperties: [
24
+ ...themeCssProperties,
25
+ { name: '--neu-success', description: 'Colour of the success variant.' },
26
+ { name: '--neu-warning', description: 'Colour of the warning variant.' },
27
+ ],
28
+ },
29
+ });
@@ -0,0 +1,5 @@
1
+ import { NeuElement } from '../../core/neu-element.js';
2
+ import { badgeDef } from './badge.def.js';
3
+ export class NeuBadge extends NeuElement {
4
+ static def = badgeDef;
5
+ }
@@ -0,0 +1 @@
1
+ export declare const badgeStyles = "\n:host {\n --_neu-bg: var(--neu-bg, #e0e5ec);\n --_neu-accent-c: var(--neu-accent, #6d5dfc);\n --_neu-d: var(--neu-distance, 6px);\n --_neu-k: var(--neu-intensity, 1);\n --_neu-r: var(--neu-radius, 12px);\n --_neu-danger: var(--neu-danger, #e5484d);\n --_neu-dur: var(--neu-duration, 150ms);\n --_neu-sd: var(--neu-shadow-dark, color-mix(in oklab, var(--_neu-bg), black min(100%, calc(var(--_neu-shade, 18%) * var(--_neu-k)))));\n --_neu-sl: var(--neu-shadow-light, color-mix(in oklab, var(--_neu-bg), white min(100%, calc(var(--_neu-light, 100%) * var(--_neu-k)))));\n --_neu-fg: var(--neu-fg, color-mix(in oklab, var(--_neu-bg), var(--_neu-ink, black) var(--_neu-fg-mix, 32%)));\n --_neu-fg-muted: var(--neu-fg-muted, color-mix(in oklab, var(--_neu-bg), var(--_neu-ink, black) var(--_neu-fg-muted-mix, 18%)));\n --_neu-accent-soft: color-mix(in oklab, var(--_neu-accent-c) 25%, var(--_neu-bg));\n --_neu-focus: 0 0 0 3px color-mix(in oklab, var(--_neu-accent-c) 45%, transparent);\n --_neu-raised: var(--_neu-d) var(--_neu-d) calc(var(--_neu-d) * 2) var(--_neu-sd), calc(var(--_neu-d) * -1) calc(var(--_neu-d) * -1) calc(var(--_neu-d) * 2) var(--_neu-sl);\n --_neu-raised-sm: calc(var(--_neu-d) * 0.5) calc(var(--_neu-d) * 0.5) var(--_neu-d) var(--_neu-sd), calc(var(--_neu-d) * -0.5) calc(var(--_neu-d) * -0.5) var(--_neu-d) var(--_neu-sl);\n --_neu-inset: inset var(--_neu-d) var(--_neu-d) calc(var(--_neu-d) * 2) var(--_neu-sd), inset calc(var(--_neu-d) * -1) calc(var(--_neu-d) * -1) calc(var(--_neu-d) * 2) var(--_neu-sl);\n --_neu-inset-sm: inset calc(var(--_neu-d) * 0.5) calc(var(--_neu-d) * 0.5) var(--_neu-d) var(--_neu-sd), inset calc(var(--_neu-d) * -0.5) calc(var(--_neu-d) * -0.5) var(--_neu-d) var(--_neu-sl);\n font-family: var(--neu-font);\n color: var(--_neu-fg);\n -webkit-tap-highlight-color: transparent;\n}\n:host(:not(:defined)) { visibility: inherit !important; min-block-size: auto !important; }\n:host([hidden]) { display: none !important; }\n:host(:focus) { outline: none; }\n*, *::before, *::after { box-sizing: border-box; }\n\n:host { display: inline-block; vertical-align: middle; --_neu-success: var(--neu-success, #3bb273); --_neu-warning: var(--neu-warning, #e6a23c); }\n.base { display: inline-flex; align-items: center; min-block-size: 1.25rem; padding-inline: 0.5rem; font-size: 0.75rem; font-weight: 600; border-radius: calc(var(--_neu-r) * 0.5); box-shadow: var(--_neu-raised-sm); background: var(--_neu-bg); color: var(--_neu-fg-muted); }\n:host([pill]) .base { border-radius: 999px; }\n:host([variant='accent']) .base { color: var(--_neu-accent-c); }\n:host([variant='success']) .base { color: var(--_neu-success); }\n:host([variant='warning']) .base { color: var(--_neu-warning); }\n:host([variant='danger']) .base { color: var(--_neu-danger); }\n";
@@ -0,0 +1,10 @@
1
+ import { hostTheme } from '../../core/styles.js';
2
+ export const badgeStyles = `${hostTheme}
3
+ :host { display: inline-block; vertical-align: middle; --_neu-success: var(--neu-success, #3bb273); --_neu-warning: var(--neu-warning, #e6a23c); }
4
+ .base { display: inline-flex; align-items: center; min-block-size: 1.25rem; padding-inline: 0.5rem; font-size: 0.75rem; font-weight: 600; border-radius: calc(var(--_neu-r) * 0.5); box-shadow: var(--_neu-raised-sm); background: var(--_neu-bg); color: var(--_neu-fg-muted); }
5
+ :host([pill]) .base { border-radius: 999px; }
6
+ :host([variant='accent']) .base { color: var(--_neu-accent-c); }
7
+ :host([variant='success']) .base { color: var(--_neu-success); }
8
+ :host([variant='warning']) .base { color: var(--_neu-warning); }
9
+ :host([variant='danger']) .base { color: var(--_neu-danger); }
10
+ `;
@@ -0,0 +1,25 @@
1
+ import { SUBMITS } from '../../core/form-control.js';
2
+ import type { ComponentDef } from '../../core/define.js';
3
+ import { NeuElement } from '../../core/neu-element.js';
4
+ export declare class NeuButton extends NeuElement {
5
+ static def: ComponentDef;
6
+ /** Form-associated only to reach internals.form; a button contributes no value. */
7
+ static formAssociated: boolean;
8
+ /** type="submit" submits the form, so this class and its subclasses can be the default button. */
9
+ static readonly [SUBMITS] = true;
10
+ variant: 'default' | 'primary' | 'flat';
11
+ size: 'sm' | 'md' | 'lg';
12
+ disabled: boolean;
13
+ loading: boolean;
14
+ type: 'button' | 'submit' | 'reset';
15
+ constructor();
16
+ get form(): HTMLFormElement | null;
17
+ private get inactive();
18
+ /** The base class already swallows clicks while disabled; loading needs the same. */
19
+ private onClickCapture;
20
+ /** Like a native button: submit or reset once the click has dispatched, unless it was cancelled. */
21
+ private onClick;
22
+ private onKeyDown;
23
+ private onKeyUp;
24
+ protected update(changed: ReadonlySet<string>): void;
25
+ }
@@ -0,0 +1,28 @@
1
+ export declare const buttonDef: import("../../core/define.js").ComponentDef<{
2
+ readonly variant: {
3
+ readonly type: "string";
4
+ readonly default: "default";
5
+ readonly values: readonly ["default", "primary", "flat"];
6
+ readonly reflect: true;
7
+ };
8
+ readonly size: {
9
+ readonly type: "string";
10
+ readonly default: "md";
11
+ readonly values: readonly ["sm", "md", "lg"];
12
+ readonly reflect: true;
13
+ };
14
+ readonly disabled: {
15
+ readonly type: "boolean";
16
+ readonly reflect: true;
17
+ };
18
+ readonly loading: {
19
+ readonly type: "boolean";
20
+ readonly reflect: true;
21
+ };
22
+ readonly type: {
23
+ readonly type: "string";
24
+ readonly default: "button";
25
+ readonly values: readonly ["button", "submit", "reset"];
26
+ readonly reflect: true;
27
+ };
28
+ }>;
@@ -0,0 +1,45 @@
1
+ import { defineComponent } from '../../core/define.js';
2
+ import { html } from '../../core/html.js';
3
+ import { themeCssProperties } from '../../core/styles.js';
4
+ import { buttonStyles } from './button.styles.js';
5
+ export const buttonDef = defineComponent({
6
+ tag: 'neu-button',
7
+ props: {
8
+ variant: {
9
+ type: 'string',
10
+ default: 'default',
11
+ values: ['default', 'primary', 'flat'],
12
+ reflect: true,
13
+ },
14
+ size: { type: 'string', default: 'md', values: ['sm', 'md', 'lg'], reflect: true },
15
+ disabled: { type: 'boolean', reflect: true },
16
+ loading: { type: 'boolean', reflect: true },
17
+ type: {
18
+ type: 'string',
19
+ default: 'button',
20
+ values: ['button', 'submit', 'reset'],
21
+ reflect: true,
22
+ },
23
+ },
24
+ styles: buttonStyles,
25
+ template: () => html `<span part="base" class="base"><span part="prefix" class="prefix"><slot name="prefix"></slot></span><span part="label" class="label"><slot></slot></span><span part="suffix" class="suffix"><slot name="suffix"></slot></span><span part="spinner" class="spinner" aria-hidden="true"></span></span>`,
26
+ docs: {
27
+ summary: 'A raised button. Use variant="primary" for the main action of a view.',
28
+ events: [
29
+ { name: 'click', description: 'Fired on activation; never while disabled or loading.' },
30
+ ],
31
+ slots: [
32
+ { name: '', description: 'Button label.' },
33
+ { name: 'prefix', description: 'Content before the label, such as an icon.' },
34
+ { name: 'suffix', description: 'Content after the label.' },
35
+ ],
36
+ parts: [
37
+ { name: 'base', description: 'The raised surface.' },
38
+ { name: 'label', description: 'Wrapper around the default slot.' },
39
+ { name: 'prefix', description: 'Wrapper around the prefix slot.' },
40
+ { name: 'suffix', description: 'Wrapper around the suffix slot.' },
41
+ { name: 'spinner', description: 'Spinner shown while loading.' },
42
+ ],
43
+ cssProperties: themeCssProperties,
44
+ },
45
+ });
@@ -0,0 +1,87 @@
1
+ import { afterDispatch, isActiveInRoot } from '../../core/dom.js';
2
+ import { markSubmitAttempt, SUBMITS } from '../../core/form-control.js';
3
+ import { NeuElement } from '../../core/neu-element.js';
4
+ import { buttonDef } from './button.def.js';
5
+ export class NeuButton extends NeuElement {
6
+ static def = buttonDef;
7
+ /** Form-associated only to reach internals.form; a button contributes no value. */
8
+ static formAssociated = true;
9
+ /** type="submit" submits the form, so this class and its subclasses can be the default button. */
10
+ static [SUBMITS] = true;
11
+ constructor() {
12
+ super();
13
+ this.internals.role = 'button';
14
+ this.addEventListener('click', (event) => this.onClickCapture(event), { capture: true });
15
+ this.addEventListener('click', (event) => this.onClick(event));
16
+ this.addEventListener('keydown', (event) => this.onKeyDown(event));
17
+ this.addEventListener('keyup', (event) => this.onKeyUp(event));
18
+ // Disarm when focus moves away, not when only the window loses focus.
19
+ this.addEventListener('blur', () => {
20
+ if (!isActiveInRoot(this))
21
+ this.toggleState('pressed', false);
22
+ });
23
+ // Focus coming back means Space may have been released while it was away.
24
+ this.addEventListener('focus', () => this.toggleState('pressed', false));
25
+ }
26
+ get form() {
27
+ return this.internals.form;
28
+ }
29
+ get inactive() {
30
+ return this.isDisabled || this.props.loading === true;
31
+ }
32
+ /** The base class already swallows clicks while disabled; loading needs the same. */
33
+ onClickCapture(event) {
34
+ if (this.props.loading !== true)
35
+ return;
36
+ event.preventDefault();
37
+ event.stopImmediatePropagation();
38
+ }
39
+ /** Like a native button: submit or reset once the click has dispatched, unless it was cancelled. */
40
+ onClick(event) {
41
+ afterDispatch(event, () => {
42
+ const form = this.internals.form;
43
+ if (event.defaultPrevented || !form)
44
+ return;
45
+ if (this.props.type === 'submit') {
46
+ markSubmitAttempt(form);
47
+ // No submitter (spec §7.1): new FormData(form, host) would throw for a non-native one.
48
+ form.requestSubmit();
49
+ }
50
+ else if (this.props.type === 'reset') {
51
+ form.reset();
52
+ }
53
+ });
54
+ }
55
+ onKeyDown(event) {
56
+ if (this.inactive)
57
+ return;
58
+ if (event.key === 'Enter') {
59
+ // Not cancelled here, so a handler further up can still cancel the activation.
60
+ afterDispatch(event, () => {
61
+ if (!event.defaultPrevented && !this.inactive)
62
+ this.click();
63
+ });
64
+ }
65
+ else if (event.key === ' ' && !event.defaultPrevented) {
66
+ // Cancelled at once: deferred, an ancestor that stops propagation would let the page scroll.
67
+ // An ancestor claims Space in the capture phase, or by cancelling the keyup.
68
+ event.preventDefault();
69
+ this.toggleState('pressed', true);
70
+ }
71
+ }
72
+ onKeyUp(event) {
73
+ if (event.key !== ' ' || !this.internals.states.has('pressed'))
74
+ return;
75
+ this.toggleState('pressed', false);
76
+ afterDispatch(event, () => {
77
+ if (!event.defaultPrevented && !this.inactive)
78
+ this.click();
79
+ });
80
+ }
81
+ update(changed) {
82
+ if (changed.has('disabled') || changed.has('loading')) {
83
+ this.syncDisabledState();
84
+ this.internals.ariaBusy = this.props.loading === true ? 'true' : null;
85
+ }
86
+ }
87
+ }
@@ -0,0 +1,2 @@
1
+ export declare const buttonBaseStyles: string;
2
+ export declare const buttonStyles: string;
@@ -0,0 +1,43 @@
1
+ import { hostTheme } from '../../core/styles.js';
2
+ /**
3
+ * Look and states shared by every button-like component; sizing is each component's own. A
4
+ * neu-button-group turns the host into a flex box, and the base grows to fill it edge to edge.
5
+ */
6
+ /** `loading` is only a thing on neu-button; icon buttons ignore a stray attribute. */
7
+ const shared = (loading) => `${hostTheme}
8
+ :host { display: inline-block; vertical-align: middle; cursor: pointer; user-select: none; }
9
+ :host(:disabled), :host([disabled])${loading ? ', :host([loading])' : ''} { cursor: not-allowed; }
10
+ .base {
11
+ display: inline-flex; flex: auto; align-items: center; justify-content: center;
12
+ border-start-start-radius: var(--_neu-grp-ss, var(--_neu-rr, var(--_neu-r))); border-start-end-radius: var(--_neu-grp-se, var(--_neu-rr, var(--_neu-r)));
13
+ border-end-start-radius: var(--_neu-grp-es, var(--_neu-rr, var(--_neu-r))); border-end-end-radius: var(--_neu-grp-ee, var(--_neu-rr, var(--_neu-r)));
14
+ background: var(--_neu-bg);
15
+ color: var(--_neu-fg);
16
+ box-shadow: var(--_neu-grp-shadow, var(--_neu-raised));
17
+ transition: box-shadow var(--_neu-dur) ease, color var(--_neu-dur) ease;
18
+ }
19
+ :host([size='sm']) .base { box-shadow: var(--_neu-grp-shadow, var(--_neu-raised-sm)); }
20
+ :host([variant='primary']) .base { color: var(--_neu-accent-c); }
21
+ :host([variant='flat']) .base { box-shadow: none; }
22
+ :host([variant='flat']:hover:not(:disabled):not([disabled])) .base { box-shadow: var(--_neu-grp-shadow, var(--_neu-raised-sm)); }
23
+ :host(:active:not(:disabled):not([disabled])${loading ? ':not([loading])' : ''}) .base, :host(:state(pressed)) .base { box-shadow: var(--_neu-inset-sm); }
24
+ :host(:focus-visible) .base { box-shadow: var(--_neu-grp-shadow, var(--_neu-raised)), var(--_neu-focus); }
25
+ :host(:disabled) .base, :host([disabled]) .base { box-shadow: none; color: var(--_neu-fg-muted); }
26
+ `;
27
+ export const buttonBaseStyles = shared(false);
28
+ export const buttonStyles = `${shared(true)}
29
+ .base { min-block-size: 2.5rem; padding-inline: 1.125rem; font: inherit; font-weight: 600; font-size: 0.9375rem; line-height: 1; }
30
+ :host([size='sm']) .base { min-block-size: 2rem; padding-inline: 0.875rem; font-size: 0.8125rem; }
31
+ :host([size='lg']) .base { min-block-size: 3rem; padding-inline: 1.5rem; font-size: 1.0625rem; }
32
+ .prefix, .suffix { display: inline-flex; }
33
+ ::slotted([slot='prefix']) { margin-inline-end: 0.5em; }
34
+ ::slotted([slot='suffix']) { margin-inline-start: 0.5em; }
35
+ .spinner {
36
+ display: none; inline-size: 1em; block-size: 1em; margin-inline-start: 0.5em; border-radius: 50%;
37
+ border: 2px solid var(--_neu-accent-soft); border-block-start-color: var(--_neu-accent-c);
38
+ animation: neu-spin 0.8s linear infinite;
39
+ }
40
+ :host([loading]) .spinner { display: inline-block; }
41
+ @keyframes neu-spin { to { transform: rotate(360deg); } }
42
+ @media (prefers-reduced-motion: reduce) { .spinner { animation-duration: 2.4s; } }
43
+ `;
@@ -0,0 +1,19 @@
1
+ import { NeuElement } from '../../core/neu-element.js';
2
+ export declare class NeuButtonGroup extends NeuElement {
3
+ static def: import("../../ssr.js").ComponentDef<{
4
+ readonly label: {
5
+ readonly type: "string";
6
+ readonly reflect: true;
7
+ };
8
+ readonly orientation: {
9
+ readonly type: "string";
10
+ readonly default: "horizontal";
11
+ readonly values: readonly ["horizontal", "vertical"];
12
+ readonly reflect: true;
13
+ };
14
+ }>;
15
+ label: string;
16
+ orientation: 'horizontal' | 'vertical';
17
+ constructor();
18
+ protected update(changed: ReadonlySet<string>): void;
19
+ }
@@ -0,0 +1,12 @@
1
+ export declare const buttonGroupDef: import("../../core/define.js").ComponentDef<{
2
+ readonly label: {
3
+ readonly type: "string";
4
+ readonly reflect: true;
5
+ };
6
+ readonly orientation: {
7
+ readonly type: "string";
8
+ readonly default: "horizontal";
9
+ readonly values: readonly ["horizontal", "vertical"];
10
+ readonly reflect: true;
11
+ };
12
+ }>;
@@ -0,0 +1,25 @@
1
+ import { defineComponent } from '../../core/define.js';
2
+ import { html } from '../../core/html.js';
3
+ import { themeCssProperties } from '../../core/styles.js';
4
+ import { buttonGroupStyles } from './button-group.styles.js';
5
+ export const buttonGroupDef = defineComponent({
6
+ tag: 'neu-button-group',
7
+ props: {
8
+ label: { type: 'string', reflect: true },
9
+ orientation: {
10
+ type: 'string',
11
+ default: 'horizontal',
12
+ values: ['horizontal', 'vertical'],
13
+ reflect: true,
14
+ },
15
+ },
16
+ styles: buttonGroupStyles,
17
+ template: () => html `<span part="base" class="base"><slot></slot></span>`,
18
+ docs: {
19
+ summary: 'Merges adjacent buttons into one raised block.',
20
+ events: [],
21
+ slots: [{ name: '', description: 'neu-button and neu-icon-button elements.' }],
22
+ parts: [{ name: 'base', description: 'The raised wrapper around the buttons.' }],
23
+ cssProperties: themeCssProperties,
24
+ },
25
+ });