@cobdfamily/clf-core 7.0.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 (44) hide show
  1. package/README.md +250 -0
  2. package/dist/_variables.scss +973 -0
  3. package/dist/components/cobd-embed.d.ts +4 -0
  4. package/dist/components/cobd-embed.js +163 -0
  5. package/dist/components/cobd-nav.d.ts +1 -0
  6. package/dist/components/cobd-nav.js +383 -0
  7. package/dist/components/cobd-support.d.ts +26 -0
  8. package/dist/components/cobd-support.js +296 -0
  9. package/dist/components/font-scale-toggle.d.ts +9 -0
  10. package/dist/components/font-scale-toggle.js +159 -0
  11. package/dist/components/forms/checkbox.d.ts +1 -0
  12. package/dist/components/forms/checkbox.js +118 -0
  13. package/dist/components/forms/common.d.ts +17 -0
  14. package/dist/components/forms/common.js +137 -0
  15. package/dist/components/forms/index.d.ts +5 -0
  16. package/dist/components/forms/index.js +13 -0
  17. package/dist/components/forms/select.d.ts +1 -0
  18. package/dist/components/forms/select.js +132 -0
  19. package/dist/components/forms/submit.d.ts +1 -0
  20. package/dist/components/forms/submit.js +78 -0
  21. package/dist/components/forms/textarea.d.ts +1 -0
  22. package/dist/components/forms/textarea.js +95 -0
  23. package/dist/components/forms/textfield.d.ts +1 -0
  24. package/dist/components/forms/textfield.js +125 -0
  25. package/dist/components/index.d.ts +7 -0
  26. package/dist/components/index.js +33 -0
  27. package/dist/components/theme-toggle.d.ts +10 -0
  28. package/dist/components/theme-toggle.js +130 -0
  29. package/dist/i18n/chrome.json +94 -0
  30. package/dist/navs/cobd.ca.json +83 -0
  31. package/dist/navs/more-cobd.json +60 -0
  32. package/dist/navs.d.ts +27 -0
  33. package/dist/navs.js +193 -0
  34. package/dist/theming/font-scale-paint.d.ts +0 -0
  35. package/dist/theming/font-scale-paint.js +32 -0
  36. package/dist/theming/init.d.ts +1 -0
  37. package/dist/theming/init.js +19 -0
  38. package/dist/theming/runtime.d.ts +22 -0
  39. package/dist/theming/runtime.js +333 -0
  40. package/dist/tokens.css +972 -0
  41. package/dist/tokens.json +97 -0
  42. package/dist/tokens.scss +95 -0
  43. package/package.json +123 -0
  44. package/src/navs/schema.json +64 -0
@@ -0,0 +1,296 @@
1
+ "use strict";
2
+ // <cobd-support [label="..."] [color="..."]
3
+ // [donate-url="..."]
4
+ // [volunteer-url="..."]
5
+ // [subscribe-url="..."]
6
+ // [share-text="..."]>
7
+ // <!-- optional: child <a> elements override
8
+ // the default action list, like cobd-nav's
9
+ // path="local" mode -->
10
+ // </cobd-support>
11
+ //
12
+ // Drop-in "Support COBD" button. On click, opens
13
+ // an action sheet (`<ion-action-sheet>` if Ionic
14
+ // is on the page; native `popover` element if
15
+ // not) with four default actions:
16
+ //
17
+ // - Donate -> donate-url (cobd.ca/donate)
18
+ // - Volunteer -> volunteer-url (cobd.ca/volunteer)
19
+ // - Spread the word-> Web Share API (clipboard
20
+ // fallback on desktop)
21
+ // - Stay in touch -> subscribe-url (cobd.ca/newsletter)
22
+ //
23
+ // Light DOM. The "Spread the word" handler tries
24
+ // `navigator.share()` first (which on mobile opens
25
+ // the system share sheet); when that's missing or
26
+ // throws (desktop / user-cancelled), it falls back
27
+ // to copying the share text to the clipboard.
28
+ function ionicPresent() {
29
+ return typeof document !== "undefined"
30
+ && document.querySelector("ion-app") !== null;
31
+ }
32
+ const BASE_STYLE_ID = "cobd-support-base-style";
33
+ const BASE_STYLE = `
34
+ cobd-support { display: inline-block; }
35
+ .cobd-support-menu {
36
+ border: 1px solid var(--cobd-color-medium, #92949c);
37
+ border-radius: var(--cobd-radius-md, 8px);
38
+ padding: var(--cobd-spacing-xs, 4px);
39
+ background: var(--cobd-color-background, #fff);
40
+ color: var(--cobd-color-foreground, #1a1a1a);
41
+ box-shadow: 0 4px 14px rgba(0, 0, 0, 0.15);
42
+ min-width: 14rem;
43
+ margin: 0;
44
+ }
45
+ .cobd-support-menu-item {
46
+ display: flex;
47
+ align-items: center;
48
+ gap: var(--cobd-spacing-sm, 8px);
49
+ padding: var(--cobd-spacing-sm, 8px) var(--cobd-spacing-md, 16px);
50
+ color: inherit;
51
+ background: none;
52
+ border: 0;
53
+ border-radius: var(--cobd-radius-sm, 4px);
54
+ text-decoration: none;
55
+ font: inherit;
56
+ text-align: left;
57
+ width: 100%;
58
+ cursor: pointer;
59
+ }
60
+ .cobd-support-menu-item:hover,
61
+ .cobd-support-menu-item:focus-visible {
62
+ background: var(--cobd-color-light, #f4f5f8);
63
+ outline: 2px solid var(--cobd-color-primary, #72cadb);
64
+ outline-offset: -2px;
65
+ }
66
+ .cobd-support-button {
67
+ font-family: var(--cobd-typography-family-sans,
68
+ system-ui, sans-serif);
69
+ font-size: var(--cobd-typography-size-md, 1rem);
70
+ font-weight: var(--cobd-typography-weight-medium, 500);
71
+ padding: var(--cobd-spacing-sm, 8px) var(--cobd-spacing-md, 16px);
72
+ border-radius: var(--cobd-radius-sm, 4px);
73
+ border: 0;
74
+ background: var(--cobd-color-primary, #72cadb);
75
+ color: var(--cobd-color-primary-contrast, #000);
76
+ cursor: pointer;
77
+ }
78
+ .cobd-support-button:hover {
79
+ background: var(--cobd-color-primary-strong,
80
+ var(--cobd-color-primary, #72cadb));
81
+ }
82
+ `;
83
+ function ensureBaseStyle() {
84
+ if (typeof document === "undefined")
85
+ return;
86
+ if (document.getElementById(BASE_STYLE_ID))
87
+ return;
88
+ const style = document.createElement("style");
89
+ style.id = BASE_STYLE_ID;
90
+ style.textContent = BASE_STYLE;
91
+ document.head.appendChild(style);
92
+ }
93
+ function readCustomActions(host) {
94
+ const links = Array.from(host.querySelectorAll(":scope > a"));
95
+ if (links.length === 0)
96
+ return null;
97
+ return links.map(a => ({
98
+ label: a.textContent?.trim() ?? "",
99
+ icon: a.getAttribute("data-icon") ?? undefined,
100
+ href: a.getAttribute("href") ?? undefined,
101
+ target: a.getAttribute("target") ?? undefined,
102
+ })).filter(a => a.label);
103
+ }
104
+ class CobdSupport extends HTMLElement {
105
+ constructor() {
106
+ super(...arguments);
107
+ this.rendered = false;
108
+ this.customActions = null;
109
+ }
110
+ static get observedAttributes() {
111
+ return [
112
+ "label", "color",
113
+ "donate-url", "volunteer-url",
114
+ "subscribe-url", "share-text",
115
+ ];
116
+ }
117
+ connectedCallback() {
118
+ ensureBaseStyle();
119
+ if (!this.rendered) {
120
+ // Snapshot the child <a> tags once -- the
121
+ // render path replaceChildren()s the host,
122
+ // which would otherwise wipe them.
123
+ this.customActions = readCustomActions(this);
124
+ this.render();
125
+ }
126
+ }
127
+ attributeChangedCallback() {
128
+ if (this.isConnected && this.rendered)
129
+ this.render();
130
+ }
131
+ render() {
132
+ const label = this.getAttribute("label") ?? "Support COBD";
133
+ const actions = this.customActions ?? this.defaultActions();
134
+ if (ionicPresent()) {
135
+ this.renderIonic(label, actions);
136
+ }
137
+ else {
138
+ this.renderPlain(label, actions);
139
+ }
140
+ this.rendered = true;
141
+ }
142
+ defaultActions() {
143
+ return [
144
+ {
145
+ label: "Donate",
146
+ icon: "heart-outline",
147
+ href: this.getAttribute("donate-url")
148
+ ?? "https://cobd.ca/donate",
149
+ },
150
+ {
151
+ label: "Volunteer",
152
+ icon: "hand-right-outline",
153
+ href: this.getAttribute("volunteer-url")
154
+ ?? "https://cobd.ca/volunteer",
155
+ },
156
+ {
157
+ label: "Spread the word",
158
+ icon: "share-social-outline",
159
+ share: true,
160
+ },
161
+ {
162
+ label: "Stay in touch",
163
+ icon: "mail-outline",
164
+ href: this.getAttribute("subscribe-url")
165
+ ?? "https://cobd.ca/newsletter",
166
+ },
167
+ ];
168
+ }
169
+ renderIonic(label, actions) {
170
+ const color = this.getAttribute("color") ?? "primary";
171
+ const btn = document.createElement("ion-button");
172
+ btn.setAttribute("color", color);
173
+ btn.textContent = label;
174
+ btn.addEventListener("click", () => this.openIonicSheet(actions));
175
+ this.replaceChildren(btn);
176
+ }
177
+ async openIonicSheet(actions) {
178
+ const sheet = document.createElement("ion-action-sheet");
179
+ sheet.header = "Support COBD";
180
+ sheet.buttons = [
181
+ ...actions.map(a => ({
182
+ text: a.label,
183
+ icon: a.icon,
184
+ handler: () => { this.handleAction(a); },
185
+ })),
186
+ { text: "Cancel", role: "cancel" },
187
+ ];
188
+ document.body.appendChild(sheet);
189
+ sheet.addEventListener("ionActionSheetDidDismiss", () => sheet.remove());
190
+ if (typeof sheet.present === "function") {
191
+ await sheet.present();
192
+ }
193
+ }
194
+ renderPlain(label, actions) {
195
+ const id = "cobd-support-pop-"
196
+ + Math.random().toString(36).slice(2, 10);
197
+ const btn = document.createElement("button");
198
+ btn.type = "button";
199
+ btn.className = "cobd-support-button";
200
+ btn.textContent = label;
201
+ btn.setAttribute("popovertarget", id);
202
+ btn.setAttribute("aria-haspopup", "menu");
203
+ const popover = document.createElement("div");
204
+ popover.id = id;
205
+ popover.setAttribute("popover", "auto");
206
+ popover.className = "cobd-support-menu";
207
+ popover.setAttribute("role", "menu");
208
+ popover.setAttribute("aria-label", "Support COBD");
209
+ for (const action of actions) {
210
+ popover.appendChild(this.buildPlainItem(action, popover));
211
+ }
212
+ this.replaceChildren(btn, popover);
213
+ }
214
+ buildPlainItem(action, popover) {
215
+ const item = action.href && !action.share
216
+ ? document.createElement("a")
217
+ : document.createElement("button");
218
+ item.className = "cobd-support-menu-item";
219
+ item.setAttribute("role", "menuitem");
220
+ if (item instanceof HTMLAnchorElement && action.href) {
221
+ item.href = action.href;
222
+ if (action.target)
223
+ item.target = action.target;
224
+ }
225
+ else if (item instanceof HTMLButtonElement) {
226
+ item.type = "button";
227
+ }
228
+ item.addEventListener("click", async (e) => {
229
+ const handled = await this.handleAction(action);
230
+ if (handled) {
231
+ // Close the popover after a handled action.
232
+ // ".hidePopover()" is part of the native
233
+ // popover API; types may not have it yet
234
+ // in older lib.dom.
235
+ popover.hidePopover?.();
236
+ if (!action.href)
237
+ e.preventDefault();
238
+ }
239
+ });
240
+ if (action.icon) {
241
+ const ic = document.createElement("ion-icon");
242
+ ic.setAttribute("name", action.icon);
243
+ ic.setAttribute("aria-hidden", "true");
244
+ item.appendChild(ic);
245
+ }
246
+ item.appendChild(document.createTextNode(action.label));
247
+ return item;
248
+ }
249
+ async handleAction(action) {
250
+ if (action.share) {
251
+ const text = this.getAttribute("share-text")
252
+ ?? "The Canadian Organization of the Blind "
253
+ + "and DeafBlind: https://cobd.ca/";
254
+ const url = "https://cobd.ca/";
255
+ if (typeof navigator !== "undefined"
256
+ && typeof navigator.share === "function") {
257
+ try {
258
+ await navigator.share({
259
+ title: "COBD",
260
+ text,
261
+ url,
262
+ });
263
+ return true;
264
+ }
265
+ catch {
266
+ // User cancelled or share API rejected;
267
+ // fall through to clipboard.
268
+ }
269
+ }
270
+ if (typeof navigator !== "undefined"
271
+ && navigator.clipboard) {
272
+ try {
273
+ await navigator.clipboard.writeText(text);
274
+ return true;
275
+ }
276
+ catch {
277
+ return false;
278
+ }
279
+ }
280
+ return false;
281
+ }
282
+ if (action.href) {
283
+ if (action.target === "_blank") {
284
+ window.open(action.href, "_blank", "noopener,noreferrer");
285
+ }
286
+ else {
287
+ window.location.href = action.href;
288
+ }
289
+ return true;
290
+ }
291
+ return true;
292
+ }
293
+ }
294
+ if (!customElements.get("cobd-support")) {
295
+ customElements.define("cobd-support", CobdSupport);
296
+ }
@@ -0,0 +1,9 @@
1
+ export declare class CobdFontScaleToggle extends HTMLElement {
2
+ private buttons;
3
+ private group;
4
+ private cleanup;
5
+ connectedCallback(): void;
6
+ disconnectedCallback(): void;
7
+ private onKeyDown;
8
+ private render;
9
+ }
@@ -0,0 +1,159 @@
1
+ // <cobd-font-scale-toggle> -- vanilla custom element
2
+ // that lets the user pick a text-size scale
3
+ // (Small / Medium / Large). Persists via the runtime's
4
+ // font-scale storage (matching the theme's choice of
5
+ // localStorage vs cookie); applies the scale to <html>
6
+ // as --cobd-font-scale, which the consumer's stylesheet
7
+ // multiplies into the root font-size:
8
+ //
9
+ // html {
10
+ // font-size: calc(100% * var(--cobd-font-scale, 1));
11
+ // }
12
+ //
13
+ // ARIA: a `role="radiogroup"` with three button-style
14
+ // radios. Arrow keys move focus + selection; Space /
15
+ // Enter activate.
16
+ import { getFontScalePreference, setFontScale, onFontScaleChange, } from "../theming/runtime.js";
17
+ const ORDER = ["sm", "md", "lg"];
18
+ // Visible label per scale + an aria-label that names
19
+ // the scale (the visible label is just "A" rendered at
20
+ // the right size, so screen readers need the explicit
21
+ // name).
22
+ const LABEL = {
23
+ sm: "Small",
24
+ md: "Medium",
25
+ lg: "Large",
26
+ };
27
+ const SIZE = {
28
+ sm: "0.85em",
29
+ md: "1em",
30
+ lg: "1.2em",
31
+ };
32
+ const STYLE = `
33
+ :host {
34
+ display: inline-flex;
35
+ align-items: center;
36
+ gap: 0.25rem;
37
+ --_border: var(--cobd-color-medium, currentColor);
38
+ --_active-bg:
39
+ var(--cobd-color-primary-tint,
40
+ var(--ion-color-primary-tint, #80cfdf));
41
+ --_active-fg:
42
+ var(--cobd-color-primary-contrast, #000);
43
+ }
44
+ [role="radiogroup"] {
45
+ display: inline-flex;
46
+ border: 1px solid var(--_border);
47
+ border-radius: var(--cobd-radius-md, 8px);
48
+ overflow: hidden;
49
+ }
50
+ button {
51
+ border: 0;
52
+ background: transparent;
53
+ color: inherit;
54
+ padding: 0.4rem 0.75rem;
55
+ font: inherit;
56
+ cursor: pointer;
57
+ line-height: 1;
58
+ border-right: 1px solid var(--_border);
59
+ }
60
+ button:last-of-type { border-right: 0; }
61
+ button:focus-visible {
62
+ outline: 2px solid var(--cobd-color-primary, currentColor);
63
+ outline-offset: 2px;
64
+ }
65
+ button[aria-checked="true"] {
66
+ background: var(--_active-bg);
67
+ color: var(--_active-fg);
68
+ font-weight: 600;
69
+ }
70
+ .glyph {
71
+ display: inline-block;
72
+ line-height: 1;
73
+ }
74
+ `;
75
+ export class CobdFontScaleToggle extends HTMLElement {
76
+ constructor() {
77
+ super(...arguments);
78
+ this.buttons = {};
79
+ this.cleanup = null;
80
+ }
81
+ connectedCallback() {
82
+ if (!this.shadowRoot) {
83
+ const root = this.attachShadow({ mode: "open" });
84
+ const style = document.createElement("style");
85
+ style.textContent = STYLE;
86
+ this.group = document.createElement("div");
87
+ this.group.setAttribute("role", "radiogroup");
88
+ this.group.setAttribute("aria-label", this.getAttribute("aria-label") || "Text size");
89
+ for (const pref of ORDER) {
90
+ const btn = document.createElement("button");
91
+ btn.type = "button";
92
+ btn.setAttribute("role", "radio");
93
+ btn.setAttribute("aria-checked", "false");
94
+ btn.setAttribute("aria-label", LABEL[pref]);
95
+ btn.dataset.cobdScale = pref;
96
+ const glyph = document.createElement("span");
97
+ glyph.className = "glyph";
98
+ glyph.setAttribute("aria-hidden", "true");
99
+ glyph.style.fontSize = SIZE[pref];
100
+ glyph.textContent = "A";
101
+ btn.appendChild(glyph);
102
+ btn.addEventListener("click", () => {
103
+ setFontScale(pref);
104
+ });
105
+ btn.addEventListener("keydown", e => this.onKeyDown(e, pref));
106
+ this.buttons[pref] = btn;
107
+ this.group.appendChild(btn);
108
+ }
109
+ root.appendChild(style);
110
+ root.appendChild(this.group);
111
+ }
112
+ this.render();
113
+ this.cleanup = onFontScaleChange(() => this.render());
114
+ }
115
+ disconnectedCallback() {
116
+ this.cleanup?.();
117
+ this.cleanup = null;
118
+ }
119
+ onKeyDown(e, current) {
120
+ let target = null;
121
+ if (e.key === "ArrowRight" || e.key === "ArrowDown") {
122
+ const i = ORDER.indexOf(current);
123
+ target = ORDER[(i + 1) % ORDER.length];
124
+ }
125
+ else if (e.key === "ArrowLeft" || e.key === "ArrowUp") {
126
+ const i = ORDER.indexOf(current);
127
+ target = ORDER[(i - 1 + ORDER.length) % ORDER.length];
128
+ }
129
+ else if (e.key === "Home") {
130
+ target = ORDER[0];
131
+ }
132
+ else if (e.key === "End") {
133
+ target = ORDER[ORDER.length - 1];
134
+ }
135
+ if (target) {
136
+ e.preventDefault();
137
+ setFontScale(target);
138
+ this.buttons[target]?.focus();
139
+ }
140
+ }
141
+ render() {
142
+ const pref = getFontScalePreference();
143
+ for (const k of ORDER) {
144
+ const btn = this.buttons[k];
145
+ if (!btn)
146
+ continue;
147
+ const active = k === pref;
148
+ btn.setAttribute("aria-checked", active ? "true" : "false");
149
+ // Roving tabindex: only the active radio is in
150
+ // the tab order; arrow keys move within the
151
+ // group.
152
+ btn.tabIndex = active ? 0 : -1;
153
+ }
154
+ }
155
+ }
156
+ if (typeof customElements !== "undefined"
157
+ && !customElements.get("cobd-font-scale-toggle")) {
158
+ customElements.define("cobd-font-scale-toggle", CobdFontScaleToggle);
159
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,118 @@
1
+ // <cobd-checkbox name="..." label="..." [value="..."]
2
+ // [checked] [help="..."] [error="..."]
3
+ // [required]>
4
+ //
5
+ // Single checkbox. Form-associated -- when checked,
6
+ // the form sees `name=value` on POST (where value
7
+ // defaults to "on", matching native <input
8
+ // type="checkbox">). When unchecked, the form sees
9
+ // no field for `name`, also matching native.
10
+ //
11
+ // Renders <ion-checkbox> + label when Ionic is on,
12
+ // plain <input type="checkbox"> + label otherwise.
13
+ import { ionicPresent, ensureBaseStyle, readAttr, readBoolAttr, readFieldDef, buildHelp, buildError, describedByValue, } from "./common.js";
14
+ class CobdCheckbox extends HTMLElement {
15
+ static get observedAttributes() {
16
+ return [
17
+ "name", "label", "value", "checked",
18
+ "help", "error", "required", "id",
19
+ ];
20
+ }
21
+ constructor() {
22
+ super();
23
+ this.rendered = false;
24
+ this.internals = this.attachInternals();
25
+ }
26
+ connectedCallback() {
27
+ ensureBaseStyle();
28
+ if (!this.rendered)
29
+ this.render();
30
+ }
31
+ attributeChangedCallback() {
32
+ if (this.isConnected && this.rendered)
33
+ this.render();
34
+ }
35
+ render() {
36
+ const def = readFieldDef(this, "cobd-checkbox");
37
+ const value = readAttr(this, "value") ?? "on";
38
+ const checked = readBoolAttr(this, "checked");
39
+ const row = document.createElement("div");
40
+ row.className = "cobd-field-row";
41
+ // Layout: checkbox sits next to the label
42
+ // text, not on its own line. Use a flex
43
+ // wrapper so the click target spans both.
44
+ const wrap = document.createElement("label");
45
+ wrap.setAttribute("for", def.id);
46
+ wrap.style.display = "flex";
47
+ wrap.style.alignItems = "center";
48
+ wrap.style.gap = "var(--cobd-spacing-sm, 8px)";
49
+ const control = ionicPresent()
50
+ ? this.buildIonCheckbox(def, value, checked)
51
+ : this.buildPlainCheckbox(def, value, checked);
52
+ wrap.appendChild(control);
53
+ const labelText = document.createElement("span");
54
+ labelText.textContent = def.label;
55
+ if (def.required) {
56
+ const star = document.createElement("span");
57
+ star.className = "cobd-required-asterisk";
58
+ star.setAttribute("aria-hidden", "true");
59
+ star.textContent = "*";
60
+ labelText.appendChild(star);
61
+ const sr = document.createElement("span");
62
+ sr.className = "sr-only";
63
+ sr.textContent = " (required)";
64
+ labelText.appendChild(sr);
65
+ }
66
+ wrap.appendChild(labelText);
67
+ row.appendChild(wrap);
68
+ const help = buildHelp(def);
69
+ if (help)
70
+ row.appendChild(help);
71
+ row.appendChild(buildError(def));
72
+ this.replaceChildren(row);
73
+ this.syncForm(checked, value);
74
+ this.rendered = true;
75
+ }
76
+ buildIonCheckbox(def, value, checked) {
77
+ const it = document.createElement("ion-checkbox");
78
+ it.setAttribute("id", def.id);
79
+ if (def.name)
80
+ it.setAttribute("name", def.name);
81
+ it.setAttribute("value", value);
82
+ if (checked)
83
+ it.setAttribute("checked", "");
84
+ if (def.required)
85
+ it.setAttribute("required", "");
86
+ it.setAttribute("aria-describedby", describedByValue(def));
87
+ it.addEventListener("ionChange", e => {
88
+ const isChecked = e.detail?.checked
89
+ ?? it.checked;
90
+ this.syncForm(isChecked, value);
91
+ });
92
+ return it;
93
+ }
94
+ buildPlainCheckbox(def, value, checked) {
95
+ const it = document.createElement("input");
96
+ it.type = "checkbox";
97
+ it.id = def.id;
98
+ if (def.name)
99
+ it.name = def.name;
100
+ it.value = value;
101
+ it.checked = checked;
102
+ if (def.required)
103
+ it.required = true;
104
+ it.setAttribute("aria-describedby", describedByValue(def));
105
+ it.addEventListener("change", () => this.syncForm(it.checked, value));
106
+ return it;
107
+ }
108
+ syncForm(checked, value) {
109
+ // Native checkbox semantics: unchecked = field
110
+ // not sent. setFormValue(null) achieves the
111
+ // same on the form-associated element side.
112
+ this.internals.setFormValue(checked ? value : null);
113
+ }
114
+ }
115
+ CobdCheckbox.formAssociated = true;
116
+ if (!customElements.get("cobd-checkbox")) {
117
+ customElements.define("cobd-checkbox", CobdCheckbox);
118
+ }
@@ -0,0 +1,17 @@
1
+ export declare function ionicPresent(): boolean;
2
+ export declare function ensureBaseStyle(): void;
3
+ export declare function readAttr(el: HTMLElement, name: string): string | null;
4
+ export declare function readBoolAttr(el: HTMLElement, name: string): boolean;
5
+ export interface FieldDef {
6
+ id: string;
7
+ name: string;
8
+ label: string;
9
+ help: string | null;
10
+ error: string | null;
11
+ required: boolean;
12
+ }
13
+ export declare function readFieldDef(el: HTMLElement, fallbackIdPrefix: string): FieldDef;
14
+ export declare function buildLabel(def: FieldDef): HTMLLabelElement;
15
+ export declare function buildHelp(def: FieldDef): HTMLElement | null;
16
+ export declare function buildError(def: FieldDef): HTMLElement;
17
+ export declare function describedByValue(def: FieldDef): string;