@c2n/radio 0.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +28 -0
- package/dist/index.js +4 -0
- package/dist/radio-DKp0-FOe.js +230 -0
- package/dist/radio-context-De4Idbg9.js +14 -0
- package/dist/radio-context.js +2 -0
- package/dist/radio-group-DhI6N66I.js +297 -0
- package/dist/radio-group.js +2 -0
- package/dist/radio.js +2 -0
- package/package.json +75 -0
- package/types/src/index.d.ts +4 -0
- package/types/src/index.d.ts.map +1 -0
- package/types/src/radio-context.d.ts +14 -0
- package/types/src/radio-context.d.ts.map +1 -0
- package/types/src/radio-group.d.ts +71 -0
- package/types/src/radio-group.d.ts.map +1 -0
- package/types/src/radio.d.ts +112 -0
- package/types/src/radio.d.ts.map +1 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Nguyen Thai Vinh
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# @c2n/radio
|
|
2
|
+
|
|
3
|
+
Radio built with Lit: `c2-radio` options on native `<input type="radio">` elements, and a `c2-radio-group` that turns them into one control with a single value, one name and native-style keyboard navigation.
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
npm install @c2n/radio
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
```html
|
|
10
|
+
<script type="module">
|
|
11
|
+
import '@c2n/radio'
|
|
12
|
+
</script>
|
|
13
|
+
|
|
14
|
+
<c2-radio-group name="plan" value="pro">
|
|
15
|
+
<span slot="label">Plan</span>
|
|
16
|
+
<c2-radio value="free">Free</c2-radio>
|
|
17
|
+
<c2-radio value="pro">Pro<span slot="description">Unlimited projects</span></c2-radio>
|
|
18
|
+
<c2-radio value="team" disabled>Team</c2-radio>
|
|
19
|
+
</c2-radio-group>
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
- **Group**: `value` is the checked option's value (set it to select from code), `name` applies to every option, `disabled` disables them all, `orientation="horizontal"` lays them out in a row. It fires one `change` (`detail.value`) per user selection and swallows the options' own `change` events. `role="radiogroup"` is named by the `label` slot or `aria-label`.
|
|
23
|
+
- **Keyboard**: Arrow keys move and select (wrapping, disabled options skipped), Space checks the focused option, and only the checked option is a tab stop, exactly like native radios.
|
|
24
|
+
- **Option**: `value`, `checked`, `disabled`, `label` (or slotted text) and a `description` slot. Options can be nested in wrapper elements inside the group. Standalone options sharing a `name` in the same root uncheck each other.
|
|
25
|
+
- **Indicator**: `--c2-radio__control--border-radius` reshapes the ring (square at `4px`); the `dot` slot replaces the filled dot with a mark such as a check; `icon` and `checked-icon` replace the whole control with an icon per state (`--c2-radio__icon--color`, `--c2-radio__icon__checked--color`, `--c2-radio__icon--size`). With only `icon` the same icon shows in both states and recolours; with only `checked-icon` the ring stays while unchecked.
|
|
26
|
+
- **Forms**: the inputs live in shadow roots and are not submitted with a surrounding form; read `value` from the group or mirror it into a hidden input.
|
|
27
|
+
|
|
28
|
+
Import `@c2n/radio` for both elements, or `@c2n/radio/radio.js` for the option alone. Every visual aspect is a CSS custom property: `--c2-radio__control--*` and `--c2-radio__dot--*` for the circle (an accent border with a transparent fill gives the outlined look), `--c2-radio__label--*` / `--c2-radio__description--*` for the text, `--c2-radio__container--*` for the row (border, padding and checked background make card-style options), and `--c2-radio-group--*` for spacing and the header. Variables set on the group inherit into every option. The full list is in `custom-elements.json` and on the docs site.
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
import { t as radioGroupContext } from "./radio-context-De4Idbg9.js";
|
|
2
|
+
import { LitElement, html, nothing, unsafeCSS } from "lit";
|
|
3
|
+
import { customElement, property, query, state } from "lit/decorators.js";
|
|
4
|
+
import { classMap } from "lit/directives/class-map.js";
|
|
5
|
+
import { ifDefined } from "lit/directives/if-defined.js";
|
|
6
|
+
import { redispatchEvent } from "@c2n/core/dom-helper.js";
|
|
7
|
+
//#region ../../../node_modules/@lit/context/lib/context-request-event.js
|
|
8
|
+
/**
|
|
9
|
+
* @license
|
|
10
|
+
* Copyright 2021 Google LLC
|
|
11
|
+
* SPDX-License-Identifier: BSD-3-Clause
|
|
12
|
+
*/
|
|
13
|
+
var s$1 = class extends Event {
|
|
14
|
+
constructor(s, t, e, o) {
|
|
15
|
+
super("context-request", {
|
|
16
|
+
bubbles: !0,
|
|
17
|
+
composed: !0
|
|
18
|
+
}), this.context = s, this.contextTarget = t, this.callback = e, this.subscribe = o ?? !1;
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
//#endregion
|
|
22
|
+
//#region ../../../node_modules/@lit/context/lib/controllers/context-consumer.js
|
|
23
|
+
/**
|
|
24
|
+
* @license
|
|
25
|
+
* Copyright 2021 Google LLC
|
|
26
|
+
* SPDX-License-Identifier: BSD-3-Clause
|
|
27
|
+
*/ var s = class {
|
|
28
|
+
constructor(t, s, i, h) {
|
|
29
|
+
if (this.subscribe = !1, this.provided = !1, this.value = void 0, this.t = (t, s) => {
|
|
30
|
+
this.unsubscribe && (this.unsubscribe !== s && (this.provided = !1, this.unsubscribe()), this.subscribe || this.unsubscribe()), this.value = t, this.host.requestUpdate(), this.provided && !this.subscribe || (this.provided = !0, this.callback && this.callback(t, s)), this.unsubscribe = s;
|
|
31
|
+
}, this.host = t, void 0 !== s.context) {
|
|
32
|
+
const t = s;
|
|
33
|
+
this.context = t.context, this.callback = t.callback, this.subscribe = t.subscribe ?? !1;
|
|
34
|
+
} else this.context = s, this.callback = i, this.subscribe = h ?? !1;
|
|
35
|
+
this.host.addController(this);
|
|
36
|
+
}
|
|
37
|
+
hostConnected() {
|
|
38
|
+
this.dispatchRequest();
|
|
39
|
+
}
|
|
40
|
+
hostDisconnected() {
|
|
41
|
+
this.unsubscribe && (this.unsubscribe(), this.unsubscribe = void 0);
|
|
42
|
+
}
|
|
43
|
+
dispatchRequest() {
|
|
44
|
+
this.host.dispatchEvent(new s$1(this.context, this.host, this.t, this.subscribe));
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
//#endregion
|
|
48
|
+
//#region ../../../node_modules/@lit/context/lib/decorators/consume.js
|
|
49
|
+
/**
|
|
50
|
+
* @license
|
|
51
|
+
* Copyright 2022 Google LLC
|
|
52
|
+
* SPDX-License-Identifier: BSD-3-Clause
|
|
53
|
+
*/ function c({ context: c, subscribe: e }) {
|
|
54
|
+
return (o, n) => {
|
|
55
|
+
"object" == typeof n ? n.addInitializer((function() {
|
|
56
|
+
new s(this, {
|
|
57
|
+
context: c,
|
|
58
|
+
callback: (t) => {
|
|
59
|
+
o.set.call(this, t);
|
|
60
|
+
},
|
|
61
|
+
subscribe: e
|
|
62
|
+
});
|
|
63
|
+
})) : o.constructor.addInitializer(((o) => {
|
|
64
|
+
new s(o, {
|
|
65
|
+
context: c,
|
|
66
|
+
callback: (t) => {
|
|
67
|
+
o[n] = t;
|
|
68
|
+
},
|
|
69
|
+
subscribe: e
|
|
70
|
+
});
|
|
71
|
+
}));
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
//#endregion
|
|
75
|
+
//#region src/radio.scss?inline
|
|
76
|
+
var radio_default = "/* ex : var((width: 24px), width, c2-checkbox) returns var(--c2-checkbox-width, 24px) */\n:host {\n display: inline-flex;\n}\n\n:host([hidden]) {\n display: none;\n}\n\n.c2-radio {\n display: inline-flex;\n align-items: center;\n box-sizing: border-box;\n flex: 1;\n min-width: 0;\n cursor: pointer;\n flex-direction: var(--c2-radio__container--flex-direction,row);\n gap: var(--c2-radio__container--gap,4px);\n padding-top: var(--c2-radio__container--padding-top,0px);\n padding-right: var(--c2-radio__container--padding-right,0px);\n padding-bottom: var(--c2-radio__container--padding-bottom,0px);\n padding-left: var(--c2-radio__container--padding-left,0px);\n border: var(--c2-radio__container--border,none);\n border-radius: var(--c2-radio__container--border-radius,8px);\n background: var(--c2-radio__container--background);\n transition: background 250ms cubic-bezier(0.2, 0, 0, 1), border-color 250ms cubic-bezier(0.2, 0, 0, 1);\n}\n.c2-radio.has-description {\n align-items: flex-start;\n}\n.c2-radio:hover:has(.c2-radio-input:enabled) {\n background: var(--c2-radio__container__hover--background,var(--c2-radio__container--background));\n}\n.c2-radio:has(.c2-radio-input:checked) {\n border: var(--c2-radio__container__checked--border,var(--c2-radio__container--border,none));\n background: var(--c2-radio__container__checked--background,var(--c2-radio__container--background));\n}\n.c2-radio:has(.c2-radio-input:checked:enabled):hover {\n background: var(--c2-radio__container__hover--background,var(--c2-radio__container__checked--background,var(--c2-radio__container--background)));\n}\n.c2-radio:has(.c2-radio-input:disabled) {\n cursor: default;\n}\n\n.c2-radio-control {\n position: relative;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n flex: none;\n width: var(--c2-radio__state-layer--size,32px);\n height: var(--c2-radio__state-layer--size,32px);\n line-height: 0;\n}\n\n.c2-radio-input {\n position: absolute;\n inset: 0;\n width: 100%;\n height: 100%;\n margin: 0;\n padding: 0;\n opacity: 0;\n cursor: inherit;\n}\n\n.c2-radio__background {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n box-sizing: border-box;\n width: var(--c2-radio__control--size,18px);\n height: var(--c2-radio__control--size,18px);\n border-radius: var(--c2-radio__control--border-radius,999px);\n border: var(--c2-radio__control--border,1px solid #bcbcc6);\n background-color: var(--c2-radio__control--background-color,transparent);\n pointer-events: none;\n transition: background-color 250ms cubic-bezier(0.2, 0, 0, 1), border-color 250ms cubic-bezier(0.2, 0, 0, 1);\n}\n\n.c2-radio__dot {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: var(--c2-radio__dot--size,8px);\n height: var(--c2-radio__dot--size,8px);\n border-radius: var(--c2-radio__control--border-radius,999px);\n background: var(--c2-radio__dot--color,#ffffff);\n color: var(--c2-radio__dot--color,#ffffff);\n line-height: 0;\n transform: scale(0);\n transition: transform 200ms cubic-bezier(0, 0, 0, 1);\n --c2-feather-icon--size: var(--c2-radio__dot--size,8px);\n --c2-mat-icon--font-size: var(--c2-radio__dot--size,8px);\n}\n.c2-radio__dot ::slotted(*) {\n width: 100%;\n height: 100%;\n}\n\n.has-dot .c2-radio__dot {\n background: transparent;\n}\n\n.c2-radio__icon {\n display: none;\n align-items: center;\n justify-content: center;\n box-sizing: border-box;\n width: var(--c2-radio__icon--size,var(--c2-radio__control--size,18px));\n height: var(--c2-radio__icon--size,var(--c2-radio__control--size,18px));\n border-radius: var(--c2-radio__control--border-radius,999px);\n color: var(--c2-radio__icon--color,#71717a);\n line-height: 0;\n pointer-events: none;\n transition: color 250ms cubic-bezier(0.2, 0, 0, 1);\n --c2-feather-icon--size: var(--c2-radio__icon--size,var(--c2-radio__control--size,18px));\n --c2-mat-icon--font-size: var(--c2-radio__icon--size,var(--c2-radio__control--size,18px));\n}\n.c2-radio__icon ::slotted(*) {\n width: 100%;\n height: 100%;\n}\n\n.has-icon .c2-radio-input:not(:checked) ~ .c2-radio__background,\n.has-icon:not(.has-checked-icon) .c2-radio-input:checked ~ .c2-radio__background,\n.has-checked-icon .c2-radio-input:checked ~ .c2-radio__background {\n display: none;\n}\n\n.has-icon .c2-radio-input:not(:checked) ~ .c2-radio__icon--unchecked,\n.has-icon:not(.has-checked-icon) .c2-radio-input:checked ~ .c2-radio__icon--unchecked,\n.has-checked-icon .c2-radio-input:checked ~ .c2-radio__icon--checked {\n display: inline-flex;\n}\n\n.c2-radio-input:checked ~ .c2-radio__icon {\n color: var(--c2-radio__icon__checked--color,#0265dc);\n}\n\n.c2-radio-state-layer {\n position: absolute;\n inset: 0;\n z-index: -1;\n border-radius: var(--c2-radio__state-layer--border-radius,999px);\n background-color: transparent;\n opacity: 0;\n pointer-events: none;\n transition: background-color 250ms cubic-bezier(0.2, 0, 0, 1), opacity 250ms cubic-bezier(0.2, 0, 0, 1);\n}\n\n.c2-radio-input:checked ~ .c2-radio__background {\n border: var(--c2-radio__control__checked--border,1px solid transparent);\n background-color: var(--c2-radio__control__checked--background-color,#0265dc);\n}\n.c2-radio-input:checked ~ .c2-radio__background .c2-radio__dot {\n transform: scale(1);\n}\n\n.c2-radio-input:enabled:not(:checked):hover ~ .c2-radio__background {\n border: var(--c2-radio__control__hover--border,1px solid #a1a1aa);\n}\n\n.c2-radio-input:enabled:checked:hover ~ .c2-radio__background {\n background-color: var(--c2-radio__control__checked__hover--background-color,var(--c2-radio__control__checked--background-color,#0154b8));\n}\n\n.c2-radio-input:enabled:hover ~ .c2-radio-state-layer {\n background-color: var(--c2-radio__state-layer__hover--color);\n opacity: 0.08;\n}\n\n.c2-radio-input:focus-visible ~ .c2-radio__background,\n.c2-radio-input:focus-visible ~ .c2-radio__icon {\n outline: var(--c2-radio__control__focus--outline,2px solid rgba(2, 101, 220, 0.4));\n outline-offset: var(--c2-radio__control__focus--outline-offset,2px);\n}\n\n.c2-radio-input:disabled ~ .c2-radio__background,\n.c2-radio-input:disabled ~ .c2-radio__icon {\n opacity: var(--c2-radio__control__disabled--opacity,0.38);\n}\n\n.c2-radio-text {\n display: flex;\n flex-direction: column;\n flex: 1 1 auto;\n min-width: 0;\n}\n\n.c2-radio-label {\n color: var(--c2-radio__label--color,inherit);\n font-size: var(--c2-radio__label--font-size,inherit);\n font-weight: var(--c2-radio__label--font-weight,inherit);\n line-height: var(--c2-radio__label--line-height,inherit);\n transition: color 250ms cubic-bezier(0.2, 0, 0, 1);\n}\n\n.has-description .c2-radio-label {\n display: flex;\n align-items: center;\n min-height: var(--c2-radio__state-layer--size,32px);\n}\n\n.c2-radio:has(.c2-radio-input:checked) .c2-radio-label {\n color: var(--c2-radio__label__checked--color,var(--c2-radio__label--color,inherit));\n}\n\n.c2-radio:has(.c2-radio-input:disabled) .c2-radio-text {\n opacity: var(--c2-radio__label__disabled--opacity,0.38);\n}\n\n.c2-radio-description {\n color: var(--c2-radio__description--color,#71717a);\n font-size: var(--c2-radio__description--font-size,12px);\n line-height: var(--c2-radio__description--line-height,1.4);\n margin-top: var(--c2-radio__description--margin-top,2px);\n}\n.c2-radio-description[hidden] {\n display: none;\n}\n\n.has-description .c2-radio-description {\n margin-top: calc(var(--c2-radio__description--margin-top,2px) - (var(--c2-radio__state-layer--size,32px) - 1.5em) / 2);\n}\n\n@media (prefers-reduced-motion: reduce) {\n .c2-radio,\n .c2-radio__background,\n .c2-radio__dot,\n .c2-radio__icon,\n .c2-radio-state-layer,\n .c2-radio-label {\n transition: none;\n }\n}";
|
|
77
|
+
//#endregion
|
|
78
|
+
//#region \0@oxc-project+runtime@0.148.0/helpers/esm/decorate.js
|
|
79
|
+
function __decorate(decorators, target, key, desc) {
|
|
80
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
81
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
82
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
83
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
84
|
+
}
|
|
85
|
+
//#endregion
|
|
86
|
+
//#region src/radio.ts
|
|
87
|
+
var Radio = class Radio extends LitElement {
|
|
88
|
+
constructor(..._args) {
|
|
89
|
+
super(..._args);
|
|
90
|
+
this.checked = false;
|
|
91
|
+
this.disabled = false;
|
|
92
|
+
this.value = "";
|
|
93
|
+
this.name = "";
|
|
94
|
+
this.label = "";
|
|
95
|
+
this.tabbable = true;
|
|
96
|
+
this.hasDescription = false;
|
|
97
|
+
this.hasDot = false;
|
|
98
|
+
this.hasIcon = false;
|
|
99
|
+
this.hasCheckedIcon = false;
|
|
100
|
+
}
|
|
101
|
+
static {
|
|
102
|
+
this.styles = unsafeCSS(radio_default);
|
|
103
|
+
}
|
|
104
|
+
/** Field name in effect: the group's, or this radio's own. */
|
|
105
|
+
get effectiveName() {
|
|
106
|
+
return this.group?.name || this.name;
|
|
107
|
+
}
|
|
108
|
+
/** Disabled by its own attribute or by the group. */
|
|
109
|
+
get effectiveDisabled() {
|
|
110
|
+
return this.disabled || !!this.group?.disabled;
|
|
111
|
+
}
|
|
112
|
+
focus(options) {
|
|
113
|
+
this.formElement?.focus(options);
|
|
114
|
+
}
|
|
115
|
+
disconnectedCallback() {
|
|
116
|
+
super.disconnectedCallback();
|
|
117
|
+
this.group = void 0;
|
|
118
|
+
}
|
|
119
|
+
/** `slotchange` does not fire for server-rendered slots, so read them once after the first render. */
|
|
120
|
+
firstUpdated() {
|
|
121
|
+
for (const slot of this.renderRoot.querySelectorAll("slot")) this.updateSlot(slot);
|
|
122
|
+
}
|
|
123
|
+
update(changed) {
|
|
124
|
+
if (changed.has("checked") && this.formElement) this.formElement.checked = this.checked;
|
|
125
|
+
super.update(changed);
|
|
126
|
+
}
|
|
127
|
+
updated(changed) {
|
|
128
|
+
if (changed.has("checked") && changed.get("checked") !== void 0 && this.isConnected) {
|
|
129
|
+
if (this.group) this.group.checkedChanged(this);
|
|
130
|
+
else if (this.checked) this.uncheckSiblings();
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
/** Standalone radios sharing a `name` in the same root behave as one group. */
|
|
134
|
+
uncheckSiblings() {
|
|
135
|
+
if (!this.name) return;
|
|
136
|
+
const root = this.getRootNode();
|
|
137
|
+
if (typeof root.querySelectorAll !== "function") return;
|
|
138
|
+
for (const radio of root.querySelectorAll("c2-radio")) if (radio !== this && radio.checked && radio.name === this.name && !radio.group) radio.checked = false;
|
|
139
|
+
}
|
|
140
|
+
handleSlotChange(event) {
|
|
141
|
+
this.updateSlot(event.target);
|
|
142
|
+
}
|
|
143
|
+
updateSlot(slot) {
|
|
144
|
+
const filled = slot.assignedNodes({ flatten: true }).some((node) => node.nodeType === Node.ELEMENT_NODE || (node.textContent ?? "").trim() !== "");
|
|
145
|
+
if (slot.name === "description") this.hasDescription = filled;
|
|
146
|
+
else if (slot.name === "dot") this.hasDot = filled;
|
|
147
|
+
else if (slot.name === "icon") this.hasIcon = filled;
|
|
148
|
+
else if (slot.name === "checked-icon") this.hasCheckedIcon = filled;
|
|
149
|
+
}
|
|
150
|
+
handleChange(event) {
|
|
151
|
+
this.checked = this.formElement.checked;
|
|
152
|
+
redispatchEvent(this, event);
|
|
153
|
+
}
|
|
154
|
+
render() {
|
|
155
|
+
const disabled = this.effectiveDisabled;
|
|
156
|
+
return html`
|
|
157
|
+
<label
|
|
158
|
+
class=${classMap({
|
|
159
|
+
"c2-radio": true,
|
|
160
|
+
"has-description": this.hasDescription,
|
|
161
|
+
"has-dot": this.hasDot,
|
|
162
|
+
"has-icon": this.hasIcon,
|
|
163
|
+
"has-checked-icon": this.hasCheckedIcon
|
|
164
|
+
})}
|
|
165
|
+
>
|
|
166
|
+
<span class="c2-radio-control">
|
|
167
|
+
<input
|
|
168
|
+
class="c2-radio-input"
|
|
169
|
+
type="radio"
|
|
170
|
+
name=${ifDefined(this.effectiveName || void 0)}
|
|
171
|
+
.value=${this.value}
|
|
172
|
+
tabindex=${this.tabbable ? nothing : "-1"}
|
|
173
|
+
aria-label=${ifDefined(this.ariaLabel)}
|
|
174
|
+
aria-describedby=${ifDefined(this.ariaDescribedBy)}
|
|
175
|
+
?checked=${this.checked}
|
|
176
|
+
?disabled=${disabled}
|
|
177
|
+
@change=${this.handleChange}
|
|
178
|
+
/>
|
|
179
|
+
<span class="c2-radio__background" part="control">
|
|
180
|
+
<span class="c2-radio__dot" part="dot"><slot name="dot" @slotchange=${this.handleSlotChange}></slot></span>
|
|
181
|
+
</span>
|
|
182
|
+
<span class="c2-radio__icon c2-radio__icon--unchecked" part="icon"><slot name="icon" @slotchange=${this.handleSlotChange}></slot></span>
|
|
183
|
+
<span class="c2-radio__icon c2-radio__icon--checked" part="checked-icon"><slot name="checked-icon" @slotchange=${this.handleSlotChange}></slot></span>
|
|
184
|
+
<span class="c2-radio-state-layer"></span>
|
|
185
|
+
</span>
|
|
186
|
+
<span class="c2-radio-text">
|
|
187
|
+
<span class="c2-radio-label" part="label"><slot>${this.label}</slot></span>
|
|
188
|
+
<span class="c2-radio-description" part="description" ?hidden=${!this.hasDescription}>
|
|
189
|
+
<slot name="description" @slotchange=${this.handleSlotChange}></slot>
|
|
190
|
+
</span>
|
|
191
|
+
</span>
|
|
192
|
+
</label>
|
|
193
|
+
`;
|
|
194
|
+
}
|
|
195
|
+
};
|
|
196
|
+
__decorate([query("input")], Radio.prototype, "formElement", void 0);
|
|
197
|
+
__decorate([property({
|
|
198
|
+
type: Boolean,
|
|
199
|
+
reflect: true
|
|
200
|
+
})], Radio.prototype, "checked", void 0);
|
|
201
|
+
__decorate([property({
|
|
202
|
+
type: Boolean,
|
|
203
|
+
reflect: true
|
|
204
|
+
})], Radio.prototype, "disabled", void 0);
|
|
205
|
+
__decorate([property()], Radio.prototype, "value", void 0);
|
|
206
|
+
__decorate([property()], Radio.prototype, "name", void 0);
|
|
207
|
+
__decorate([property()], Radio.prototype, "label", void 0);
|
|
208
|
+
__decorate([property({
|
|
209
|
+
type: Boolean,
|
|
210
|
+
attribute: false
|
|
211
|
+
})], Radio.prototype, "tabbable", void 0);
|
|
212
|
+
__decorate([property({
|
|
213
|
+
type: String,
|
|
214
|
+
attribute: "aria-label"
|
|
215
|
+
})], Radio.prototype, "ariaLabel", void 0);
|
|
216
|
+
__decorate([property({
|
|
217
|
+
type: String,
|
|
218
|
+
attribute: "aria-describedby"
|
|
219
|
+
})], Radio.prototype, "ariaDescribedBy", void 0);
|
|
220
|
+
__decorate([state()], Radio.prototype, "hasDescription", void 0);
|
|
221
|
+
__decorate([state()], Radio.prototype, "hasDot", void 0);
|
|
222
|
+
__decorate([state()], Radio.prototype, "hasIcon", void 0);
|
|
223
|
+
__decorate([state()], Radio.prototype, "hasCheckedIcon", void 0);
|
|
224
|
+
__decorate([c({
|
|
225
|
+
context: radioGroupContext,
|
|
226
|
+
subscribe: true
|
|
227
|
+
})], Radio.prototype, "group", void 0);
|
|
228
|
+
Radio = __decorate([customElement("c2-radio")], Radio);
|
|
229
|
+
//#endregion
|
|
230
|
+
export { __decorate as n, s$1 as r, Radio as t };
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
//#region ../../../node_modules/@lit/context/lib/create-context.js
|
|
2
|
+
/**
|
|
3
|
+
* @license
|
|
4
|
+
* Copyright 2021 Google LLC
|
|
5
|
+
* SPDX-License-Identifier: BSD-3-Clause
|
|
6
|
+
*/
|
|
7
|
+
function n(n) {
|
|
8
|
+
return n;
|
|
9
|
+
}
|
|
10
|
+
//#endregion
|
|
11
|
+
//#region src/radio-context.ts
|
|
12
|
+
var radioGroupContext = n(Symbol("c2-radio-group"));
|
|
13
|
+
//#endregion
|
|
14
|
+
export { radioGroupContext as t };
|
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
import { n as __decorate, r as s$1, t as Radio } from "./radio-DKp0-FOe.js";
|
|
2
|
+
import { t as radioGroupContext } from "./radio-context-De4Idbg9.js";
|
|
3
|
+
import { LitElement, html, isServer, nothing, unsafeCSS } from "lit";
|
|
4
|
+
import { customElement, property, state } from "lit/decorators.js";
|
|
5
|
+
import { ifDefined } from "lit/directives/if-defined.js";
|
|
6
|
+
//#region ../../../node_modules/@lit/context/lib/value-notifier.js
|
|
7
|
+
/**
|
|
8
|
+
* @license
|
|
9
|
+
* Copyright 2021 Google LLC
|
|
10
|
+
* SPDX-License-Identifier: BSD-3-Clause
|
|
11
|
+
*/
|
|
12
|
+
var s = class {
|
|
13
|
+
get value() {
|
|
14
|
+
return this.o;
|
|
15
|
+
}
|
|
16
|
+
set value(s) {
|
|
17
|
+
this.setValue(s);
|
|
18
|
+
}
|
|
19
|
+
setValue(s, t = !1) {
|
|
20
|
+
const i = t || !Object.is(s, this.o);
|
|
21
|
+
this.o = s, i && this.updateObservers();
|
|
22
|
+
}
|
|
23
|
+
constructor(s) {
|
|
24
|
+
this.subscriptions = /* @__PURE__ */ new Map(), this.updateObservers = () => {
|
|
25
|
+
for (const [s, { disposer: t }] of this.subscriptions) s(this.o, t);
|
|
26
|
+
}, void 0 !== s && (this.value = s);
|
|
27
|
+
}
|
|
28
|
+
addCallback(s, t, i) {
|
|
29
|
+
if (!i) return void s(this.value);
|
|
30
|
+
this.subscriptions.has(s) || this.subscriptions.set(s, {
|
|
31
|
+
disposer: () => {
|
|
32
|
+
this.subscriptions.delete(s);
|
|
33
|
+
},
|
|
34
|
+
consumerHost: t
|
|
35
|
+
});
|
|
36
|
+
const { disposer: h } = this.subscriptions.get(s);
|
|
37
|
+
s(this.value, h);
|
|
38
|
+
}
|
|
39
|
+
clearCallbacks() {
|
|
40
|
+
this.subscriptions.clear();
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
//#endregion
|
|
44
|
+
//#region ../../../node_modules/@lit/context/lib/controllers/context-provider.js
|
|
45
|
+
/**
|
|
46
|
+
* @license
|
|
47
|
+
* Copyright 2021 Google LLC
|
|
48
|
+
* SPDX-License-Identifier: BSD-3-Clause
|
|
49
|
+
*/ var e$1 = class extends Event {
|
|
50
|
+
constructor(t, s) {
|
|
51
|
+
super("context-provider", {
|
|
52
|
+
bubbles: !0,
|
|
53
|
+
composed: !0
|
|
54
|
+
}), this.context = t, this.contextTarget = s;
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
var i = class extends s {
|
|
58
|
+
constructor(s, e, i) {
|
|
59
|
+
super(void 0 !== e.context ? e.initialValue : i), this.onContextRequest = (t) => {
|
|
60
|
+
if (t.context !== this.context) return;
|
|
61
|
+
const s = t.contextTarget ?? t.composedPath()[0];
|
|
62
|
+
s !== this.host && (t.stopPropagation(), this.addCallback(t.callback, s, t.subscribe));
|
|
63
|
+
}, this.onProviderRequest = (s) => {
|
|
64
|
+
if (s.context !== this.context) return;
|
|
65
|
+
if ((s.contextTarget ?? s.composedPath()[0]) === this.host) return;
|
|
66
|
+
const e = /* @__PURE__ */ new Set();
|
|
67
|
+
for (const [s, { consumerHost: i }] of this.subscriptions) e.has(s) || (e.add(s), i.dispatchEvent(new s$1(this.context, i, s, !0)));
|
|
68
|
+
s.stopPropagation();
|
|
69
|
+
}, this.host = s, void 0 !== e.context ? this.context = e.context : this.context = e, this.attachListeners(), this.host.addController?.(this);
|
|
70
|
+
}
|
|
71
|
+
attachListeners() {
|
|
72
|
+
this.host.addEventListener("context-request", this.onContextRequest), this.host.addEventListener("context-provider", this.onProviderRequest);
|
|
73
|
+
}
|
|
74
|
+
hostConnected() {
|
|
75
|
+
this.host.dispatchEvent(new e$1(this.context, this.host));
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
//#endregion
|
|
79
|
+
//#region ../../../node_modules/@lit/context/lib/decorators/provide.js
|
|
80
|
+
/**
|
|
81
|
+
* @license
|
|
82
|
+
* Copyright 2017 Google LLC
|
|
83
|
+
* SPDX-License-Identifier: BSD-3-Clause
|
|
84
|
+
*/ function e({ context: e }) {
|
|
85
|
+
return (n, i$1) => {
|
|
86
|
+
const r = /* @__PURE__ */ new WeakMap();
|
|
87
|
+
if ("object" == typeof i$1) return {
|
|
88
|
+
get() {
|
|
89
|
+
return n.get.call(this);
|
|
90
|
+
},
|
|
91
|
+
set(t) {
|
|
92
|
+
return r.get(this).setValue(t), n.set.call(this, t);
|
|
93
|
+
},
|
|
94
|
+
init(n) {
|
|
95
|
+
return r.set(this, new i(this, {
|
|
96
|
+
context: e,
|
|
97
|
+
initialValue: n
|
|
98
|
+
})), n;
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
{
|
|
102
|
+
n.constructor.addInitializer(((n) => {
|
|
103
|
+
r.set(n, new i(n, { context: e }));
|
|
104
|
+
}));
|
|
105
|
+
const o = Object.getOwnPropertyDescriptor(n, i$1);
|
|
106
|
+
let s;
|
|
107
|
+
if (void 0 === o) {
|
|
108
|
+
const t = /* @__PURE__ */ new WeakMap();
|
|
109
|
+
s = {
|
|
110
|
+
get() {
|
|
111
|
+
return t.get(this);
|
|
112
|
+
},
|
|
113
|
+
set(e) {
|
|
114
|
+
r.get(this).setValue(e), t.set(this, e);
|
|
115
|
+
},
|
|
116
|
+
configurable: !0,
|
|
117
|
+
enumerable: !0
|
|
118
|
+
};
|
|
119
|
+
} else {
|
|
120
|
+
const t = o.set;
|
|
121
|
+
s = {
|
|
122
|
+
...o,
|
|
123
|
+
set(e) {
|
|
124
|
+
r.get(this).setValue(e), t?.call(this, e);
|
|
125
|
+
}
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
Object.defineProperty(n, i$1, s);
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
//#endregion
|
|
134
|
+
//#region src/radio-group.scss?inline
|
|
135
|
+
var radio_group_default = "/* ex : var((width: 24px), width, c2-checkbox) returns var(--c2-checkbox-width, 24px) */\n:host {\n display: block;\n}\n\n:host([hidden]) {\n display: none;\n}\n\n.c2-radio-group {\n display: flex;\n flex-direction: column;\n min-width: 0;\n}\n\n.c2-radio-group-header {\n display: flex;\n flex-direction: column;\n gap: var(--c2-radio-group__header--gap,2px);\n margin-bottom: var(--c2-radio-group__header--margin-bottom,8px);\n}\n.c2-radio-group-header[hidden] {\n display: none;\n}\n\n:host([disabled]) .c2-radio-group-header {\n opacity: var(--c2-radio-group__header__disabled--opacity,0.38);\n}\n\n.c2-radio-group-label {\n color: var(--c2-radio-group__label--color,#18181b);\n font-size: var(--c2-radio-group__label--font-size,14px);\n font-weight: var(--c2-radio-group__label--font-weight,500);\n}\n.c2-radio-group-label[hidden] {\n display: none;\n}\n\n.c2-radio-group-description {\n color: var(--c2-radio-group__description--color,#71717a);\n font-size: var(--c2-radio-group__description--font-size,12px);\n}\n.c2-radio-group-description[hidden] {\n display: none;\n}\n\n.c2-radio-group-items {\n display: flex;\n flex-direction: column;\n align-items: var(--c2-radio-group--align-items,flex-start);\n gap: var(--c2-radio-group--gap,8px);\n}\n\n:host([orientation=horizontal]) .c2-radio-group-items {\n flex-direction: row;\n flex-wrap: wrap;\n align-items: center;\n}";
|
|
136
|
+
//#endregion
|
|
137
|
+
//#region src/radio-group.ts
|
|
138
|
+
var RADIO_TAG = "c2-radio";
|
|
139
|
+
var RadioGroup = class RadioGroup extends LitElement {
|
|
140
|
+
static {
|
|
141
|
+
this.styles = unsafeCSS(radio_group_default);
|
|
142
|
+
}
|
|
143
|
+
constructor() {
|
|
144
|
+
super();
|
|
145
|
+
this.value = "";
|
|
146
|
+
this.name = "";
|
|
147
|
+
this.disabled = false;
|
|
148
|
+
this.orientation = "vertical";
|
|
149
|
+
this.hasLabel = false;
|
|
150
|
+
this.hasDescription = false;
|
|
151
|
+
this.context = this.createContext();
|
|
152
|
+
this.handleRadioChange = (event) => {
|
|
153
|
+
const radio = event.target;
|
|
154
|
+
if (radio === this || !(radio instanceof Radio) || radio.closest("c2-radio-group") !== this) return;
|
|
155
|
+
event.stopPropagation();
|
|
156
|
+
this.select(radio);
|
|
157
|
+
};
|
|
158
|
+
this.handleKeydown = (event) => {
|
|
159
|
+
if (this.disabled) return;
|
|
160
|
+
const radios = this.radios.filter((radio) => !radio.disabled);
|
|
161
|
+
const current = radios.indexOf(event.target);
|
|
162
|
+
if (current === -1) return;
|
|
163
|
+
const rtl = getComputedStyle(this).direction === "rtl";
|
|
164
|
+
let next;
|
|
165
|
+
switch (event.key) {
|
|
166
|
+
case "ArrowDown":
|
|
167
|
+
next = current + 1;
|
|
168
|
+
break;
|
|
169
|
+
case "ArrowUp":
|
|
170
|
+
next = current - 1;
|
|
171
|
+
break;
|
|
172
|
+
case "ArrowRight":
|
|
173
|
+
next = rtl ? current - 1 : current + 1;
|
|
174
|
+
break;
|
|
175
|
+
case "ArrowLeft":
|
|
176
|
+
next = rtl ? current + 1 : current - 1;
|
|
177
|
+
break;
|
|
178
|
+
default: return;
|
|
179
|
+
}
|
|
180
|
+
event.preventDefault();
|
|
181
|
+
const radio = radios[(next + radios.length) % radios.length];
|
|
182
|
+
radio.focus();
|
|
183
|
+
this.select(radio);
|
|
184
|
+
};
|
|
185
|
+
if (!isServer) {
|
|
186
|
+
this.addEventListener("change", this.handleRadioChange);
|
|
187
|
+
this.addEventListener("keydown", this.handleKeydown);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
/** The `c2-radio` elements of this group (nested groups keep their own), in DOM order. */
|
|
191
|
+
get radios() {
|
|
192
|
+
return [...this.querySelectorAll(RADIO_TAG)].filter((radio) => radio.closest("c2-radio-group") === this);
|
|
193
|
+
}
|
|
194
|
+
createContext() {
|
|
195
|
+
return {
|
|
196
|
+
name: this.name,
|
|
197
|
+
disabled: this.disabled,
|
|
198
|
+
checkedChanged: (radio) => this.handleCheckedChanged(radio)
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
/** `slotchange` does not fire for server-rendered slots, so read them once after the first render. */
|
|
202
|
+
firstUpdated() {
|
|
203
|
+
for (const slot of this.renderRoot.querySelectorAll("slot")) this.updateSlot(slot);
|
|
204
|
+
}
|
|
205
|
+
willUpdate(changed) {
|
|
206
|
+
if (changed.has("name") || changed.has("disabled")) this.context = this.createContext();
|
|
207
|
+
}
|
|
208
|
+
updated(changed) {
|
|
209
|
+
if (changed.has("value") || changed.has("disabled")) this.syncRadios();
|
|
210
|
+
}
|
|
211
|
+
handleSlotChange(event) {
|
|
212
|
+
this.updateSlot(event.target);
|
|
213
|
+
}
|
|
214
|
+
updateSlot(slot) {
|
|
215
|
+
if (slot.name === "label" || slot.name === "description") {
|
|
216
|
+
const filled = slot.assignedNodes({ flatten: true }).some((node) => node.nodeType === Node.ELEMENT_NODE || (node.textContent ?? "").trim() !== "");
|
|
217
|
+
if (slot.name === "label") this.hasLabel = filled;
|
|
218
|
+
else this.hasDescription = filled;
|
|
219
|
+
} else this.adoptRadios();
|
|
220
|
+
}
|
|
221
|
+
/** Options may upgrade after the group; wait for them, then adopt a pre-checked option as the group value. */
|
|
222
|
+
async adoptRadios() {
|
|
223
|
+
await customElements.whenDefined(RADIO_TAG);
|
|
224
|
+
const radios = this.radios;
|
|
225
|
+
await Promise.all(radios.map((radio) => radio.updateComplete));
|
|
226
|
+
if (!this.value) {
|
|
227
|
+
const checked = radios.find((radio) => radio.checked);
|
|
228
|
+
if (checked) this.value = checked.value;
|
|
229
|
+
}
|
|
230
|
+
this.syncRadios();
|
|
231
|
+
}
|
|
232
|
+
/** Called through the context when an option's `checked` changed (user or code). */
|
|
233
|
+
handleCheckedChanged(radio) {
|
|
234
|
+
if (radio.checked) {
|
|
235
|
+
if (this.value !== radio.value) this.value = radio.value;
|
|
236
|
+
else this.syncRadios();
|
|
237
|
+
} else if (this.value === radio.value && !this.radios.some((other) => other.checked)) this.value = "";
|
|
238
|
+
}
|
|
239
|
+
/** Mirrors `value` onto the options and keeps exactly one of them in the tab order. */
|
|
240
|
+
syncRadios() {
|
|
241
|
+
const radios = this.radios;
|
|
242
|
+
if (radios.length === 0) return;
|
|
243
|
+
const checked = this.value ? radios.find((radio) => radio.value === this.value) : void 0;
|
|
244
|
+
const focusable = checked && !checked.disabled ? checked : radios.find((radio) => !radio.disabled);
|
|
245
|
+
for (const radio of radios) {
|
|
246
|
+
radio.checked = radio === checked;
|
|
247
|
+
radio.tabbable = radio === focusable;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
select(radio) {
|
|
251
|
+
if (this.value === radio.value) return;
|
|
252
|
+
this.value = radio.value;
|
|
253
|
+
this.dispatchEvent(new CustomEvent("change", {
|
|
254
|
+
detail: { value: this.value },
|
|
255
|
+
bubbles: true,
|
|
256
|
+
composed: true
|
|
257
|
+
}));
|
|
258
|
+
}
|
|
259
|
+
render() {
|
|
260
|
+
return html`
|
|
261
|
+
<div
|
|
262
|
+
class="c2-radio-group"
|
|
263
|
+
role="radiogroup"
|
|
264
|
+
aria-label=${ifDefined(this.ariaLabel)}
|
|
265
|
+
aria-labelledby=${this.hasLabel ? "label" : nothing}
|
|
266
|
+
aria-describedby=${this.hasDescription ? "description" : nothing}
|
|
267
|
+
aria-disabled=${this.disabled ? "true" : nothing}
|
|
268
|
+
aria-orientation=${this.orientation}
|
|
269
|
+
>
|
|
270
|
+
<div class="c2-radio-group-header" ?hidden=${!this.hasLabel && !this.hasDescription}>
|
|
271
|
+
<div id="label" class="c2-radio-group-label" ?hidden=${!this.hasLabel}><slot name="label" @slotchange=${this.handleSlotChange}></slot></div>
|
|
272
|
+
<div id="description" class="c2-radio-group-description" ?hidden=${!this.hasDescription}>
|
|
273
|
+
<slot name="description" @slotchange=${this.handleSlotChange}></slot>
|
|
274
|
+
</div>
|
|
275
|
+
</div>
|
|
276
|
+
<div class="c2-radio-group-items"><slot @slotchange=${this.handleSlotChange}></slot></div>
|
|
277
|
+
</div>
|
|
278
|
+
`;
|
|
279
|
+
}
|
|
280
|
+
};
|
|
281
|
+
__decorate([property({ reflect: true })], RadioGroup.prototype, "value", void 0);
|
|
282
|
+
__decorate([property()], RadioGroup.prototype, "name", void 0);
|
|
283
|
+
__decorate([property({
|
|
284
|
+
type: Boolean,
|
|
285
|
+
reflect: true
|
|
286
|
+
})], RadioGroup.prototype, "disabled", void 0);
|
|
287
|
+
__decorate([property({ reflect: true })], RadioGroup.prototype, "orientation", void 0);
|
|
288
|
+
__decorate([property({
|
|
289
|
+
type: String,
|
|
290
|
+
attribute: "aria-label"
|
|
291
|
+
})], RadioGroup.prototype, "ariaLabel", void 0);
|
|
292
|
+
__decorate([state()], RadioGroup.prototype, "hasLabel", void 0);
|
|
293
|
+
__decorate([state()], RadioGroup.prototype, "hasDescription", void 0);
|
|
294
|
+
__decorate([e({ context: radioGroupContext })], RadioGroup.prototype, "context", void 0);
|
|
295
|
+
RadioGroup = __decorate([customElement("c2-radio-group")], RadioGroup);
|
|
296
|
+
//#endregion
|
|
297
|
+
export { RadioGroup as t };
|
package/dist/radio.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@c2n/radio",
|
|
3
|
+
"version": "0.0.6",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"exports": {
|
|
7
|
+
".": {
|
|
8
|
+
"default": "./dist/index.js",
|
|
9
|
+
"types": "./types/src/index.d.ts"
|
|
10
|
+
},
|
|
11
|
+
"./radio.js": {
|
|
12
|
+
"default": "./dist/radio.js",
|
|
13
|
+
"types": "./types/src/radio.d.ts"
|
|
14
|
+
},
|
|
15
|
+
"./radio-group.js": {
|
|
16
|
+
"default": "./dist/radio-group.js",
|
|
17
|
+
"types": "./types/src/radio-group.d.ts"
|
|
18
|
+
},
|
|
19
|
+
"./radio-context.js": {
|
|
20
|
+
"default": "./dist/radio-context.js",
|
|
21
|
+
"types": "./types/src/radio-context.d.ts"
|
|
22
|
+
},
|
|
23
|
+
"./custom-elements.json": "./custom-elements.json"
|
|
24
|
+
},
|
|
25
|
+
"files": [
|
|
26
|
+
"dist",
|
|
27
|
+
"types"
|
|
28
|
+
],
|
|
29
|
+
"keywords": [
|
|
30
|
+
"radio",
|
|
31
|
+
"radio group",
|
|
32
|
+
"web component",
|
|
33
|
+
"lit"
|
|
34
|
+
],
|
|
35
|
+
"license": "MIT",
|
|
36
|
+
"author": "code2nguyen@gmail.com",
|
|
37
|
+
"publishConfig": {
|
|
38
|
+
"registry": "https://registry.npmjs.org",
|
|
39
|
+
"access": "public"
|
|
40
|
+
},
|
|
41
|
+
"repository": {
|
|
42
|
+
"type": "git",
|
|
43
|
+
"url": "https://github.com/code2nguyen/web-components.git"
|
|
44
|
+
},
|
|
45
|
+
"scripts": {
|
|
46
|
+
"dev": "vite",
|
|
47
|
+
"build": "wireit",
|
|
48
|
+
"build:only": "vite build",
|
|
49
|
+
"type-check": "wireit"
|
|
50
|
+
},
|
|
51
|
+
"wireit": {
|
|
52
|
+
"type-check": {
|
|
53
|
+
"dependencies": [
|
|
54
|
+
"../../core:build"
|
|
55
|
+
],
|
|
56
|
+
"command": "tsc -p tsconfig.lib.json --composite false"
|
|
57
|
+
},
|
|
58
|
+
"build": {
|
|
59
|
+
"dependencies": [
|
|
60
|
+
"type-check"
|
|
61
|
+
],
|
|
62
|
+
"command": "vite build"
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
"dependencies": {
|
|
66
|
+
"@c2n/core": "0.0.6",
|
|
67
|
+
"@lit/context": "1.1.6",
|
|
68
|
+
"lit": "3.3.3"
|
|
69
|
+
},
|
|
70
|
+
"devDependencies": {
|
|
71
|
+
"@c2n/config": "*"
|
|
72
|
+
},
|
|
73
|
+
"customElements": "custom-elements.json",
|
|
74
|
+
"gitHead": "2921054bc75299da66dad11c1e374246ff88a51e"
|
|
75
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAA;AAC/B,OAAO,EAAE,UAAU,EAAE,KAAK,qBAAqB,EAAE,MAAM,eAAe,CAAA;AACtE,OAAO,EAAE,iBAAiB,EAAE,KAAK,iBAAiB,EAAE,MAAM,iBAAiB,CAAA"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { Radio } from './radio';
|
|
2
|
+
/** What a `c2-radio-group` shares with the `c2-radio` elements inside it. */
|
|
3
|
+
export interface RadioGroupContext {
|
|
4
|
+
/** Form field name applied to every radio of the group. */
|
|
5
|
+
name: string;
|
|
6
|
+
/** Disables every radio of the group. */
|
|
7
|
+
disabled: boolean;
|
|
8
|
+
/** Called by a radio after its `checked` state changed, so the group can update `value` and uncheck the others. */
|
|
9
|
+
checkedChanged(radio: Radio): void;
|
|
10
|
+
}
|
|
11
|
+
export declare const radioGroupContext: {
|
|
12
|
+
__context__: RadioGroupContext | undefined;
|
|
13
|
+
};
|
|
14
|
+
//# sourceMappingURL=radio-context.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"radio-context.d.ts","sourceRoot":"","sources":["../../src/radio-context.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,SAAS,CAAA;AAEpC,6EAA6E;AAC7E,MAAM,WAAW,iBAAiB;IAChC,2DAA2D;IAC3D,IAAI,EAAE,MAAM,CAAA;IACZ,yCAAyC;IACzC,QAAQ,EAAE,OAAO,CAAA;IACjB,mHAAmH;IACnH,cAAc,CAAC,KAAK,EAAE,KAAK,GAAG,IAAI,CAAA;CACnC;AAED,eAAO,MAAM,iBAAiB;;CAAyE,CAAA"}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { LitElement, type PropertyValues } from 'lit';
|
|
2
|
+
import { type RadioGroupContext } from './radio-context';
|
|
3
|
+
import { Radio } from './radio';
|
|
4
|
+
export type RadioGroupOrientation = 'vertical' | 'horizontal';
|
|
5
|
+
/**
|
|
6
|
+
* Groups `c2-radio` options into one control: a single `value`, one `name` for every option, group-level `disabled`,
|
|
7
|
+
* and keyboard behaviour that matches native radios across the shadow boundary (Arrow keys move and select, only the
|
|
8
|
+
* selected option is a tab stop). Radios may sit anywhere inside the group, wrapped in other elements. The `label`
|
|
9
|
+
* and `description` slots name the group for assistive technology (`role="radiogroup"`).
|
|
10
|
+
*
|
|
11
|
+
* @tag c2-radio-group
|
|
12
|
+
*
|
|
13
|
+
* @slot - The `c2-radio` options, optionally inside wrapper elements.
|
|
14
|
+
* @slot label - Group heading, announced as the group's name.
|
|
15
|
+
* @slot description - Supporting text under the heading.
|
|
16
|
+
*
|
|
17
|
+
* @event {CustomEvent<{ value: string }>} change - Fired after the user selects another option. `detail.value` is the new `value`. Not fired for programmatic changes.
|
|
18
|
+
*
|
|
19
|
+
* @cssproperty {pixel} [--c2-radio-group--gap=8px] - Space between options.
|
|
20
|
+
* @cssproperty {align-items} [--c2-radio-group--align-items=flex-start] - `stretch` makes card-style options fill the width.
|
|
21
|
+
* @cssproperty {pixel} [--c2-radio-group__header--gap=2px] - Space between the label and the description.
|
|
22
|
+
* @cssproperty {pixel} [--c2-radio-group__header--margin-bottom=8px] - Space between the header and the options.
|
|
23
|
+
* @cssproperty {color} [--c2-radio-group__label--color=#18181b]
|
|
24
|
+
* @cssproperty {font-size} [--c2-radio-group__label--font-size=14px]
|
|
25
|
+
* @cssproperty {font-weight} [--c2-radio-group__label--font-weight=500]
|
|
26
|
+
* @cssproperty {color} [--c2-radio-group__description--color=#71717a]
|
|
27
|
+
* @cssproperty {font-size} [--c2-radio-group__description--font-size=12px]
|
|
28
|
+
* @cssproperty {opacity} [--c2-radio-group__header__disabled--opacity=0.38]
|
|
29
|
+
* @slotcomponent c2-radio
|
|
30
|
+
*/
|
|
31
|
+
export declare class RadioGroup extends LitElement {
|
|
32
|
+
static styles: import("lit").CSSResult;
|
|
33
|
+
/** `value` of the checked option; empty when none is checked. Setting it checks the matching option. */
|
|
34
|
+
value: string;
|
|
35
|
+
/** Form field name applied to every option. */
|
|
36
|
+
name: string;
|
|
37
|
+
/** Disables every option. */
|
|
38
|
+
disabled: boolean;
|
|
39
|
+
/** Lays the options out in a row instead of a column. */
|
|
40
|
+
orientation: RadioGroupOrientation;
|
|
41
|
+
ariaLabel: string;
|
|
42
|
+
private hasLabel;
|
|
43
|
+
private hasDescription;
|
|
44
|
+
protected context: RadioGroupContext;
|
|
45
|
+
constructor();
|
|
46
|
+
/** The `c2-radio` elements of this group (nested groups keep their own), in DOM order. */
|
|
47
|
+
get radios(): Radio[];
|
|
48
|
+
private createContext;
|
|
49
|
+
/** `slotchange` does not fire for server-rendered slots, so read them once after the first render. */
|
|
50
|
+
firstUpdated(): void;
|
|
51
|
+
willUpdate(changed: PropertyValues<this>): void;
|
|
52
|
+
updated(changed: PropertyValues<this>): void;
|
|
53
|
+
private handleSlotChange;
|
|
54
|
+
private updateSlot;
|
|
55
|
+
/** Options may upgrade after the group; wait for them, then adopt a pre-checked option as the group value. */
|
|
56
|
+
private adoptRadios;
|
|
57
|
+
/** Called through the context when an option's `checked` changed (user or code). */
|
|
58
|
+
private handleCheckedChanged;
|
|
59
|
+
/** Mirrors `value` onto the options and keeps exactly one of them in the tab order. */
|
|
60
|
+
private syncRadios;
|
|
61
|
+
private select;
|
|
62
|
+
private handleRadioChange;
|
|
63
|
+
private handleKeydown;
|
|
64
|
+
render(): import("lit-html").TemplateResult<1>;
|
|
65
|
+
}
|
|
66
|
+
declare global {
|
|
67
|
+
interface HTMLElementTagNameMap {
|
|
68
|
+
'c2-radio-group': RadioGroup;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
//# sourceMappingURL=radio-group.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"radio-group.d.ts","sourceRoot":"","sources":["../../src/radio-group.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAsC,KAAK,cAAc,EAAE,MAAM,KAAK,CAAA;AAIzF,OAAO,EAAqB,KAAK,iBAAiB,EAAE,MAAM,iBAAiB,CAAA;AAE3E,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAA;AAK/B,MAAM,MAAM,qBAAqB,GAAG,UAAU,GAAG,YAAY,CAAA;AAE7D;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,qBACa,UAAW,SAAQ,UAAU;IACxC,OAAgB,MAAM,0BAAoB;IAE1C,wGAAwG;IAC3E,KAAK,SAAK;IAEvC,+CAA+C;IACnC,IAAI,SAAK;IAErB,6BAA6B;IACe,QAAQ,UAAQ;IAE5D,yDAAyD;IAC5B,WAAW,EAAE,qBAAqB,CAAa;IAGnE,SAAS,EAAG,MAAM,CAAA;IAElB,OAAO,CAAC,QAAQ,CAAQ;IACxB,OAAO,CAAC,cAAc,CAAQ;IAGvC,SAAS,CAAC,OAAO,EAAE,iBAAiB,CAAuB;;IAU3D,0FAA0F;IAC1F,IAAI,MAAM,IAAI,KAAK,EAAE,CAEpB;IAED,OAAO,CAAC,aAAa;IAIrB,sGAAsG;IAC7F,YAAY;IAIZ,UAAU,CAAC,OAAO,EAAE,cAAc,CAAC,IAAI,CAAC;IAKxC,OAAO,CAAC,OAAO,EAAE,cAAc,CAAC,IAAI,CAAC;IAI9C,OAAO,CAAC,gBAAgB;IAIxB,OAAO,CAAC,UAAU;IAUlB,8GAA8G;YAChG,WAAW;IAWzB,oFAAoF;IACpF,OAAO,CAAC,oBAAoB;IAS5B,uFAAuF;IACvF,OAAO,CAAC,UAAU;IAWlB,OAAO,CAAC,MAAM;IAMd,OAAO,CAAC,iBAAiB,CAMxB;IAED,OAAO,CAAC,aAAa,CA4BpB;IAEQ,MAAM;CAqBhB;AAED,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,qBAAqB;QAC7B,gBAAgB,EAAE,UAAU,CAAA;KAC7B;CACF"}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { LitElement, type PropertyValues } from 'lit';
|
|
2
|
+
/**
|
|
3
|
+
* A single radio option built on a native `<input type="radio">`, with its label and an optional description beside
|
|
4
|
+
* the control. Put several in a `c2-radio-group` to get a single `value`, arrow-key navigation and a roving tab stop;
|
|
5
|
+
* standalone radios that share a `name` in the same document or shadow root still uncheck each other. Theme the
|
|
6
|
+
* control, the label and the whole row (`container`), which turns a radio into a selectable card. The indicator is
|
|
7
|
+
* not tied to the ring: the `dot` slot swaps the inner mark, and `icon` / `checked-icon` replace the control with
|
|
8
|
+
* any icon per state.
|
|
9
|
+
*
|
|
10
|
+
* @tag c2-radio
|
|
11
|
+
*
|
|
12
|
+
* @slot - Label text. Falls back to the `label` attribute.
|
|
13
|
+
* @slot description - Supporting text shown under the label.
|
|
14
|
+
* @slot dot - Mark inside the ring while checked (an inline SVG, a `c2-feather-*` icon or a `c2-mat-icon`). Replaces the filled dot.
|
|
15
|
+
* @slot icon - Replaces the ring with your own indicator. Shown while unchecked, and while checked too unless `checked-icon` is set.
|
|
16
|
+
* @slot checked-icon - Indicator shown while checked, replacing the ring and dot. Without `icon` the default ring still shows while unchecked.
|
|
17
|
+
*
|
|
18
|
+
* @event {Event} change - Re-dispatched from the inner input when the user checks this radio.
|
|
19
|
+
*
|
|
20
|
+
* @cssproperty {pixel} [--c2-radio__container--gap=4px] - Space between the control and the text.
|
|
21
|
+
* @cssproperty {flex-direction-row} [--c2-radio__container--flex-direction=row] - `row-reverse` puts the indicator after the text, e.g. a trailing check mark.
|
|
22
|
+
* @cssproperty {padding} [--c2-radio__container--padding-top=0px]
|
|
23
|
+
* @cssproperty {padding} [--c2-radio__container--padding-right=0px]
|
|
24
|
+
* @cssproperty {padding} [--c2-radio__container--padding-bottom=0px]
|
|
25
|
+
* @cssproperty {padding} [--c2-radio__container--padding-left=0px]
|
|
26
|
+
* @cssproperty {border} [--c2-radio__container--border=none] - Set a border and padding for a card-style option.
|
|
27
|
+
* @cssproperty {border-radius} [--c2-radio__container--border-radius=8px]
|
|
28
|
+
* @cssproperty {background} --c2-radio__container--background
|
|
29
|
+
* @cssproperty {background} --c2-radio__container__hover--background
|
|
30
|
+
* @cssproperty {border} --c2-radio__container__checked--border
|
|
31
|
+
* @cssproperty {background} --c2-radio__container__checked--background
|
|
32
|
+
*
|
|
33
|
+
* @cssproperty {pixel} [--c2-radio__control--size=18px]
|
|
34
|
+
* @cssproperty {border-radius} [--c2-radio__control--border-radius=999px] - Shape of the ring and its dot; `4px` gives a square control.
|
|
35
|
+
* @cssproperty {border} [--c2-radio__control--border=1px solid #bcbcc6]
|
|
36
|
+
* @cssproperty {color} [--c2-radio__control--background-color=transparent]
|
|
37
|
+
* @cssproperty {border} [--c2-radio__control__hover--border=1px solid #a1a1aa]
|
|
38
|
+
* @cssproperty {border} [--c2-radio__control__checked--border=1px solid transparent] - Transparent so the fill shows through; set an accent border with a transparent fill for an outlined look.
|
|
39
|
+
* @cssproperty {color} [--c2-radio__control__checked--background-color=#0265dc]
|
|
40
|
+
* @cssproperty {color} [--c2-radio__control__checked__hover--background-color=#0154b8]
|
|
41
|
+
* @cssproperty {outline} [--c2-radio__control__focus--outline=2px solid rgba(2, 101, 220, 0.4)]
|
|
42
|
+
* @cssproperty {pixel} [--c2-radio__control__focus--outline-offset=2px]
|
|
43
|
+
* @cssproperty {opacity} [--c2-radio__control__disabled--opacity=0.38]
|
|
44
|
+
*
|
|
45
|
+
* @cssproperty {pixel} [--c2-radio__dot--size=8px]
|
|
46
|
+
* @cssproperty {color} [--c2-radio__dot--color=#ffffff] - Fill of the dot, or `currentColor` of a slotted `dot` mark.
|
|
47
|
+
*
|
|
48
|
+
* @cssproperty {pixel} --c2-radio__icon--size - Size of slotted `icon` / `checked-icon` content. Falls back to the control size.
|
|
49
|
+
* @cssproperty {color} [--c2-radio__icon--color=#71717a] - Colour of the slotted indicator while unchecked.
|
|
50
|
+
* @cssproperty {color} [--c2-radio__icon__checked--color=#0265dc] - Colour of the slotted indicator while checked.
|
|
51
|
+
*
|
|
52
|
+
* @cssproperty {pixel} [--c2-radio__state-layer--size=32px] - Touch target around the control; also the hover layer.
|
|
53
|
+
* @cssproperty {border-radius} [--c2-radio__state-layer--border-radius=999px]
|
|
54
|
+
* @cssproperty {color} --c2-radio__state-layer__hover--color - Tint of the hover layer; transparent unless set.
|
|
55
|
+
*
|
|
56
|
+
* @cssproperty {color} [--c2-radio__label--color=inherit]
|
|
57
|
+
* @cssproperty {font-size} [--c2-radio__label--font-size=inherit]
|
|
58
|
+
* @cssproperty {font-weight} [--c2-radio__label--font-weight=inherit]
|
|
59
|
+
* @cssproperty {line-height} [--c2-radio__label--line-height=inherit]
|
|
60
|
+
* @cssproperty {color} --c2-radio__label__checked--color
|
|
61
|
+
* @cssproperty {opacity} [--c2-radio__label__disabled--opacity=0.38]
|
|
62
|
+
*
|
|
63
|
+
* @cssproperty {color} [--c2-radio__description--color=#71717a]
|
|
64
|
+
* @cssproperty {font-size} [--c2-radio__description--font-size=12px]
|
|
65
|
+
* @cssproperty {line-height} [--c2-radio__description--line-height=1.4]
|
|
66
|
+
* @cssproperty {pixel} [--c2-radio__description--margin-top=2px]
|
|
67
|
+
*/
|
|
68
|
+
export declare class Radio extends LitElement {
|
|
69
|
+
static styles: import("lit").CSSResult;
|
|
70
|
+
protected formElement: HTMLInputElement;
|
|
71
|
+
/** Whether this option is selected. Checking one radio unchecks the others of its group. */
|
|
72
|
+
checked: boolean;
|
|
73
|
+
/** Disables this option. A disabled `c2-radio-group` disables every radio inside it. */
|
|
74
|
+
disabled: boolean;
|
|
75
|
+
/** Value reported by the group and by the inner input. Every radio of a group needs one. */
|
|
76
|
+
value: string;
|
|
77
|
+
/** Form field name. Inside a group, the group's `name` wins. */
|
|
78
|
+
name: string;
|
|
79
|
+
/** Label text when the default slot is empty. */
|
|
80
|
+
label: string;
|
|
81
|
+
/** Whether the inner input is in the tab order. The parent group manages this (roving tabindex); do not set by hand. */
|
|
82
|
+
tabbable: boolean;
|
|
83
|
+
ariaLabel: string;
|
|
84
|
+
ariaDescribedBy: undefined | string;
|
|
85
|
+
private hasDescription;
|
|
86
|
+
private hasDot;
|
|
87
|
+
private hasIcon;
|
|
88
|
+
private hasCheckedIcon;
|
|
89
|
+
private group;
|
|
90
|
+
/** Field name in effect: the group's, or this radio's own. */
|
|
91
|
+
get effectiveName(): string;
|
|
92
|
+
/** Disabled by its own attribute or by the group. */
|
|
93
|
+
get effectiveDisabled(): boolean;
|
|
94
|
+
focus(options?: FocusOptions): void;
|
|
95
|
+
disconnectedCallback(): void;
|
|
96
|
+
/** `slotchange` does not fire for server-rendered slots, so read them once after the first render. */
|
|
97
|
+
firstUpdated(): void;
|
|
98
|
+
protected update(changed: PropertyValues<this>): void;
|
|
99
|
+
updated(changed: PropertyValues<this>): void;
|
|
100
|
+
/** Standalone radios sharing a `name` in the same root behave as one group. */
|
|
101
|
+
private uncheckSiblings;
|
|
102
|
+
private handleSlotChange;
|
|
103
|
+
private updateSlot;
|
|
104
|
+
private handleChange;
|
|
105
|
+
render(): import("lit-html").TemplateResult<1>;
|
|
106
|
+
}
|
|
107
|
+
declare global {
|
|
108
|
+
interface HTMLElementTagNameMap {
|
|
109
|
+
'c2-radio': Radio;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
//# sourceMappingURL=radio.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"radio.d.ts","sourceRoot":"","sources":["../../src/radio.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAA4B,KAAK,cAAc,EAAE,MAAM,KAAK,CAAA;AAS/E;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiEG;AACH,qBACa,KAAM,SAAQ,UAAU;IACnC,OAAgB,MAAM,0BAAoB;IAE1B,SAAS,CAAC,WAAW,EAAG,gBAAgB,CAAA;IAExD,4FAA4F;IAChD,OAAO,UAAQ;IAE3D,wFAAwF;IAC5C,QAAQ,UAAQ;IAE5D,4FAA4F;IAChF,KAAK,SAAK;IAEtB,gEAAgE;IACpD,IAAI,SAAK;IAErB,iDAAiD;IACrC,KAAK,SAAK;IAEtB,wHAAwH;IACzE,QAAQ,UAAO;IAGrD,SAAS,EAAG,MAAM,CAAA;IAG3B,eAAe,EAAG,SAAS,GAAG,MAAM,CAAA;IAE3B,OAAO,CAAC,cAAc,CAAQ;IAC9B,OAAO,CAAC,MAAM,CAAQ;IACtB,OAAO,CAAC,OAAO,CAAQ;IACvB,OAAO,CAAC,cAAc,CAAQ;IAGvC,OAAO,CAAC,KAAK,CAA+B;IAE5C,8DAA8D;IAC9D,IAAI,aAAa,IAAI,MAAM,CAE1B;IAED,qDAAqD;IACrD,IAAI,iBAAiB,IAAI,OAAO,CAE/B;IAEQ,KAAK,CAAC,OAAO,CAAC,EAAE,YAAY;IAI5B,oBAAoB;IAK7B,sGAAsG;IAC7F,YAAY;cAIF,MAAM,CAAC,OAAO,EAAE,cAAc,CAAC,IAAI,CAAC;IAM9C,OAAO,CAAC,OAAO,EAAE,cAAc,CAAC,IAAI,CAAC;IAO9C,+EAA+E;IAC/E,OAAO,CAAC,eAAe;IASvB,OAAO,CAAC,gBAAgB;IAIxB,OAAO,CAAC,UAAU;IAQlB,OAAO,CAAC,YAAY;IAKX,MAAM;CAyChB;AAED,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,qBAAqB;QAC7B,UAAU,EAAE,KAAK,CAAA;KAClB;CACF"}
|