@apliteni/apliteni-ui 0.133.0 → 0.136.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -100,7 +100,7 @@ export function wireFeedback(opts = {}) {
100
100
  const minChars = opts.minChars == null ? 3 : opts.minChars;
101
101
  const sectionFn = typeof opts.section === 'function' ? opts.section : defaultSection;
102
102
  const onSend = typeof opts.onSend === 'function' ? opts.onSend : async () => ({ ok: false, error: 'No submit handler configured.' });
103
- let pending = null, markSpan = null;
103
+ let pending = null, markSpan = null, opening = 0;
104
104
 
105
105
  function showPill(range) {
106
106
  const r = range.getBoundingClientRect();
@@ -141,6 +141,7 @@ export function wireFeedback(opts = {}) {
141
141
 
142
142
  function open() {
143
143
  if (!pending) return;
144
+ opening++;
144
145
  hidePill();
145
146
  quoteEl.textContent = pending.text.length > 200 ? pending.text.slice(0, 200) + '…' : pending.text;
146
147
  chip.textContent = pending.label || 'this page';
@@ -154,7 +155,7 @@ export function wireFeedback(opts = {}) {
154
155
  pill.addEventListener('mousedown', (e) => e.preventDefault());
155
156
  pill.addEventListener('click', open);
156
157
 
157
- function close() { composer.classList.remove('show'); scrim.classList.remove('show'); clearHighlight(); pending = null; }
158
+ function close() { opening++; composer.classList.remove('show'); scrim.classList.remove('show'); clearHighlight(); pending = null; }
158
159
  q('[data-fb-close]').addEventListener('click', close);
159
160
  q('[data-fb-cancel]').addEventListener('click', close);
160
161
  q('[data-fb-done-close]').addEventListener('click', close);
@@ -168,12 +169,14 @@ export function wireFeedback(opts = {}) {
168
169
  }
169
170
  sendBtn.addEventListener('click', async () => {
170
171
  if (sendBtn.disabled || !pending) return;
172
+ const sentOpening = opening;
171
173
  errEl.classList.remove('show'); sendBtn.disabled = true; sendLb.innerHTML = '<span class="ui-fbspin"></span>Sending';
172
174
  const payload = { note: note.value.trim(), excerpt: pending.text, anchor: pending.anchor, sectionLabel: pending.label, sectionTitle: pending.title };
173
175
  try {
174
176
  const res = await onSend(payload);
177
+ if (opening !== sentOpening) return;
175
178
  if (!res || !res.ok) { fail(res && res.error); return; }
176
179
  form.style.display = 'none'; done.style.display = ''; clearHighlight();
177
- } catch (e) { fail(); }
180
+ } catch (e) { if (opening === sentOpening) fail(); }
178
181
  });
179
182
  }
@@ -13,18 +13,20 @@ const cx = (...a) => a.filter(Boolean).join(' ');
13
13
  // Minimal brand-glyph set for the optional social row. Feather-ish where generic,
14
14
  // simplified brand marks otherwise. Kept local so the footer is self-contained
15
15
  // (the shared icon set is line-only and has no brand glyphs).
16
+ // LinkedIn: Font Awesome Free brands/linkedin.svg, CC BY 4.0.
16
17
  const SOCIAL = {
17
18
  github: '<path d="M12 2a10 10 0 0 0-3.16 19.49c.5.09.68-.22.68-.48v-1.7c-2.78.6-3.37-1.34-3.37-1.34-.45-1.16-1.11-1.47-1.11-1.47-.9-.62.07-.6.07-.6 1 .07 1.53 1.03 1.53 1.03.9 1.53 2.34 1.09 2.91.83.09-.65.35-1.09.63-1.34-2.22-.25-4.55-1.11-4.55-4.94 0-1.09.39-1.98 1.03-2.68-.1-.25-.45-1.27.1-2.64 0 0 .84-.27 2.75 1.02a9.5 9.5 0 0 1 5 0c1.91-1.29 2.75-1.02 2.75-1.02.55 1.37.2 2.39.1 2.64.64.7 1.03 1.59 1.03 2.68 0 3.84-2.34 4.68-4.57 4.93.36.31.68.92.68 1.85v2.74c0 .27.18.58.69.48A10 10 0 0 0 12 2z"/>',
18
19
  x: '<path d="M4 3h4.5l4 5.5L17.5 3H21l-6.5 8.5L21 21h-4.5l-4.3-5.9L7 21H3.5l6.8-8.9z"/>',
19
- linkedin: '<rect x="3" y="3" width="18" height="18" rx="2"/><path d="M8 10v6M8 7v.01M12 16v-3a2 2 0 0 1 4 0v3" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"/>',
20
+ linkedin: '<path d="M416 32H31.9C14.3 32 0 46.5 0 64.3v383.4C0 465.5 14.3 480 31.9 480H416c17.6 0 32-14.5 32-32.3V64.3c0-17.8-14.4-32.3-32-32.3zM135.4 416H69V202.2h66.5V416zm-33.2-243c-21.3 0-38.5-17.3-38.5-38.5S80.9 96 102.2 96c21.2 0 38.5 17.3 38.5 38.5 0 21.3-17.2 38.5-38.5 38.5zm282.1 243h-66.4V312c0-24.8-.5-56.7-34.5-56.7-34.6 0-39.9 27-39.9 54.9V416h-66.4V202.2h63.7v29.2h.9c8.9-16.8 30.6-34.5 62.9-34.5 67.2 0 79.7 44.3 79.7 101.9V416z"/>',
20
21
  mail: '<rect x="3" y="5" width="18" height="14" rx="2" fill="none" stroke="currentColor" stroke-width="1.7"/><path d="m3 7 9 6 9-6" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"/>',
21
22
  };
22
23
 
23
24
  function socialLink({ label, href = '#', icon: ic = 'github' } = {}) {
24
25
  const glyph = SOCIAL[ic] || SOCIAL.github;
25
- const fill = ic === 'github' || ic === 'x' ? 'currentColor' : 'none';
26
+ const fill = ic === 'mail' ? 'none' : 'currentColor';
27
+ const viewBox = ic === 'linkedin' ? '0 0 448 512' : '0 0 24 24';
26
28
  return `<a class="ui-footer__social" href="${esc(safeUrl(href))}" aria-label="${esc(label || ic)}">` +
27
- `<svg viewBox="0 0 24 24" fill="${fill}" aria-hidden="true">${glyph}</svg></a>`;
29
+ `<svg viewBox="${viewBox}" fill="${fill}" aria-hidden="true">${glyph}</svg></a>`;
28
30
  }
29
31
 
30
32
  function linkList(links = []) {
@@ -15,7 +15,7 @@
15
15
  font-family: var(--font-sans);
16
16
  font-size: var(--btn-font);
17
17
  font-weight: var(--weight-medium);
18
- line-height: 1;
18
+ line-height: var(--leading-tight);
19
19
  border-radius: var(--radius-sm);
20
20
  padding: var(--btn-pad-y) var(--btn-pad-x);
21
21
  cursor: pointer;
@@ -46,15 +46,14 @@
46
46
  why: docs/foundations.md#spacing-and-rhythm */
47
47
  .ui-card--fit { width: fit-content; max-width: 100%; }
48
48
 
49
- /* An accent-tinted card, e.g. a highlighted plan / success surface. The accent edge is
50
- the card's own hairline re-coloured: an inset ring over it would draw two. #295
49
+ /* An accent-tinted card, e.g. a highlighted plan / success surface. It draws no accent
50
+ ring: the tint carries the signal and the edge is the plain card edge. #295
51
51
  The strength is --card-tint, per theme: light mixes down from white, so a tint that
52
- works in dark sinks a light card below the page. Each variant edges itself in its own
53
- colour. why: docs/foundations.md#elevation */
52
+ works in dark sinks a light card below the page. The accent card keeps the plain
53
+ edge. why: docs/foundations.md#elevation */
54
54
  .ui-card--accent {
55
55
  --ui-card-ground: color-mix(in srgb, var(--accent) var(--card-tint), var(--surface));
56
56
  background: color-mix(in srgb, var(--accent) var(--card-tint), var(--surface));
57
- border-color: color-mix(in srgb, var(--accent) 22%, transparent);
58
57
  }
59
58
  .ui-card--live {
60
59
  --ui-card-ground: color-mix(in srgb, var(--green) var(--card-tint), var(--surface));
@@ -77,6 +76,7 @@
77
76
  change of level. why: docs/foundations.md#elevation */
78
77
  .ui-card--interactive:hover { transform: translateY(-2px); }
79
78
  .ui-card--interactive:focus-visible { outline: var(--ring); outline-offset: var(--ring-offset); }
79
+ .ui-card--interactive .ui-card__sub:last-child { margin-bottom: 0; }
80
80
 
81
81
  /* card() emits the title as an h2, so the text face is named here rather than
82
82
  left to the element. why: docs/foundations.md#labels-and-titles */
@@ -111,6 +111,23 @@
111
111
  gap: var(--space-5);
112
112
  padding: var(--space-4) 2px;
113
113
  }
114
+ /* A choice that does not fit beside its label drops under it whole, never into a column. */
115
+ .ui-card__row:has(> .ui-seg) { flex-wrap: wrap; }
116
+ .ui-card__row:has(> .ui-seg) > :first-child { flex: 1 1 10rem; min-width: 0; }
117
+ .ui-card__row > .ui-seg { flex: none; flex-wrap: nowrap; }
118
+ .ui-card__row > .ui-seg button { white-space: nowrap; }
119
+ @media (max-width: 560px) {
120
+ .ui-card__row { flex-direction: column; align-items: flex-start; }
121
+ .ui-card__row > :first-child { width: 100%; }
122
+ .ui-card__row:has(> .ui-seg) > :first-child { flex: none; }
123
+ }
124
+ /* The Theme choice needs 195px and the narrowest settings row gives it 160. Its pills
125
+ give up inline padding as the viewport narrows. Other choices keep their tap width. */
126
+ @media (max-width: 560px) {
127
+ .ui-card__row--tight-choice > .ui-seg:not(.ui-seg--sm):not(.ui-seg--underline) button {
128
+ padding-inline: clamp(var(--space-1), calc(var(--space-3) - (360px - 100vw) * 0.3), var(--space-3));
129
+ }
130
+ }
114
131
  .ui-card__row + .ui-card__row { border-top: 1px solid var(--border); }
115
132
  .ui-card__row .lab { color: var(--strong); font-size: var(--text-md); font-weight: var(--weight-medium); }
116
133
  .ui-card__row .hint { color: var(--text); font-size: calc(var(--text-sm) + 0.5px); margin-top: 3px; }
@@ -45,6 +45,7 @@
45
45
  opacity var(--dur-fast) var(--ease);
46
46
  }
47
47
  .ui-fbpill.show { opacity: 1; pointer-events: auto; transform: translate(-50%, -100%) translateY(-9px) scale(1); }
48
+ .ui-fbpill:focus-visible { outline: var(--ring); outline-offset: var(--ring-offset); }
48
49
  .ui-fbpill.show:hover { transform: translate(-50%, -100%) translateY(-12px) scale(1.04); }
49
50
  .ui-fbpill:active { transform: translate(-50%, -100%) translateY(-8px) scale(.98); }
50
51
  .ui-fbpill svg { width: 16px; height: 16px; stroke-width: 2.4; color: currentColor; }
@@ -78,14 +79,14 @@
78
79
  .ui-fbcomposer.show { opacity: 1; pointer-events: auto; transform: translateX(-50%) translateY(0); }
79
80
 
80
81
  .ui-fbc__head { display: flex; align-items: center; gap: 12px; padding: 17px 20px 0; }
81
- .ui-fbc__chip { display: inline-flex; align-items: center; gap: 8px; color: var(--accent); font-size: var(--text-sm); font-weight: var(--weight-semibold); letter-spacing: .01em; white-space: nowrap; min-width: 0; overflow: hidden; text-overflow: ellipsis; }
82
+ .ui-fbc__chip { display: inline-flex; align-items: center; gap: 8px; color: var(--strong); font-size: var(--text-sm); font-weight: var(--weight-semibold); letter-spacing: .01em; white-space: nowrap; min-width: 0; overflow: hidden; text-overflow: ellipsis; }
82
83
  .ui-fbc__chip svg { width: 14px; height: 14px; flex: none; opacity: .85; stroke-width: 2.7; }
83
84
  .ui-fbc__x { margin-left: auto; background: var(--surface); /* code-bg: inherit — control paint; the containing surface is the ground, for a chip and for a band alike. */ /* muted-ink: glyph — dismiss icon */ color: var(--dim); border: 0; width: 28px; height: 28px; border-radius: 8px; cursor: pointer; display: grid; place-items: center; flex: none; font: inherit; }
84
85
  .ui-fbc__x:hover { color: var(--strong); }
85
86
  .ui-fbc__x:focus-visible { outline: var(--ring); outline-offset: var(--ring-offset); }
86
87
  .ui-fbc__x svg { width: 14px; height: 14px; stroke-width: 2.7; }
87
88
  .ui-fbc__quote { margin: 14px 20px 0; background: var(--surface); /* code-bg: inherit — decorative or text-only paint; nothing nested reads a ground from it. */ border-radius: 12px; padding: 12px 15px; display: flex; gap: 11px; }
88
- .ui-fbc__qm { color: var(--accent); font-size: var(--text-xl); font-weight: var(--weight-bold); line-height: .9; flex: none; opacity: .55; }
89
+ .ui-fbc__qm { color: var(--text); font-size: var(--text-xl); font-weight: var(--weight-bold); line-height: .9; flex: none; opacity: .55; }
89
90
  .ui-fbc__quote q { quotes: none; color: var(--text); font-style: italic; font-size: calc(var(--text-sm) + 0.5px); line-height: 1.55; }
90
91
  .ui-fbc__body { padding: 14px 20px 0; }
91
92
  .ui-fbcomposer textarea {
@@ -129,7 +130,7 @@
129
130
  @keyframes uiFbCkSpring { 0% { transform: scale(1.32); } 62% { transform: scale(.965); } 82% { transform: scale(1.02); } 100% { transform: scale(1); } }
130
131
 
131
132
  /* Error banner */
132
- .ui-fbc__err { margin: 14px 20px 0; background: color-mix(in srgb, var(--pink) 14%, transparent); /* code-bg: inherit — text-only validation message has no nested focus target. */ color: var(--pink); font-size: var(--text-sm); padding: 11px 15px; border-radius: 11px; display: none; }
133
+ .ui-fbc__err { margin: 14px 20px 0; background: color-mix(in srgb, var(--pink) 14%, transparent); /* code-bg: inherit — text-only validation message has no nested focus target. */ color: var(--chip-danger-ink); font-size: var(--text-sm); padding: 11px 15px; border-radius: 11px; display: none; }
133
134
  .ui-fbc__err.show { display: block; }
134
135
  /* A failed send's line fades in; wireFeedback() adds the class as it shows it. */
135
136
  .ui-fbc__err.is-entering { animation: m-fade var(--dur-fast) var(--ease-out) both; }
@@ -73,8 +73,9 @@
73
73
  /* Link columns */
74
74
  .ui-footer__nav {
75
75
  display: grid;
76
- grid-template-columns: repeat(auto-fit, minmax(140px, max-content));
77
- gap: 30px 56px;
76
+ flex: 1 1 400px;
77
+ grid-template-columns: repeat(auto-fit, minmax(100px, 1fr));
78
+ gap: 30px 40px;
78
79
  }
79
80
  .ui-footer__col-title {
80
81
  /* rank: label */
@@ -129,7 +130,7 @@
129
130
  fourth value of the footer's own: docs/foundations.md#breakpoints. */
130
131
  @media (max-width: 720px) {
131
132
  .ui-footer__top { flex-direction: column; gap: 30px; }
132
- .ui-footer__nav { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 26px 30px; }
133
+ .ui-footer__nav { flex: none; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 26px 30px; }
133
134
  .ui-footer__brand { max-width: none; }
134
135
  }
135
136
  @media (max-width: 560px) {
@@ -543,10 +543,10 @@
543
543
  .ui-app__drawer .ui-app__user-trigger { min-height: 44px; }
544
544
  .ui-app__main { padding: 28px var(--space-4) 56px; }
545
545
  .ui-app__main h1 { font-size: calc(var(--text-xl) + 3px); }
546
- /* A settings row is label-beside-control down to the width where the control
547
- no longer fits beside anything; below that it stacks. Scoped to the shell
548
- rather than written into card.css, because a card row outside a shell is
549
- somebody else's container to size — see the report. */
546
+ /* A settings row is label-beside-control, and below this step any control that does
547
+ not fit beside its label drops under it. A row that holds a choice wraps this way at
548
+ every width, in card.css. Scoped to the shell, because a card row outside a shell
549
+ is somebody else's container to size. */
550
550
  .ui-app__main .ui-card__row { flex-wrap: wrap; gap: var(--space-3); }
551
551
  }
552
552