@gjsify/adwaita-web 0.62.0 → 0.63.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.
@@ -35,13 +35,7 @@ import { asm6502 } from './asm6502.js';
35
35
  import { adwaitaEditorTheme, adwaitaHighlightStyle, ensureSourceViewStyleInjected } from './theme.js';
36
36
  import { formatHexAddress, formatLineNumber, stripSourceWhitespace } from '@gjsify/adwaita-core';
37
37
  import { createGtkImage } from '../elements/gtk-image.js';
38
-
39
- /** A recognisable copy glyph rendered inline so the subpath needs no icon CSS. */
40
- const COPY_SVG =
41
- '<svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true">' +
42
- '<path d="M4 1a2 2 0 0 0-2 2v7h2V3h6V1H4z"/>' +
43
- '<path d="M7 4a2 2 0 0 0-2 2v7a2 2 0 0 0 2 2h5a2 2 0 0 0 2-2V6a2 2 0 0 0-2-2H7zm0 2h5v7H7V6z"/>' +
44
- '</svg>';
38
+ import { createCopyGlyph } from './copy-glyph.js';
45
39
 
46
40
  /** Parse a boolean attribute: present (any value but "false") ⇒ true. */
47
41
  function boolAttr(value: string | null): boolean {
@@ -400,7 +394,7 @@ export class AdwSourceView extends HTMLElement {
400
394
  private _renderCopyIcon(): void {
401
395
  const name = this._copyButtonIcon || 'edit-copy';
402
396
  if (name === 'edit-copy' || name === 'copy' || name === '') {
403
- this._copyButton.innerHTML = COPY_SVG;
397
+ this._copyButton.replaceChildren(createCopyGlyph(this._copyButton.ownerDocument));
404
398
  return;
405
399
  }
406
400
  // A non-default icon uses the adwaita-web mask class (requires the main
@@ -0,0 +1,46 @@
1
+ // The copy glyph the `<adw-source-view>` copy button draws.
2
+ //
3
+ // The regression this holds: the glyph WAS a constant SVG string assigned with
4
+ // `innerHTML`. Constant and safe-until-edited, and the reviewer who flagged the two
5
+ // `use-markup` sinks in the same package flagged the shape, so it is built from nodes now.
6
+ // These rows are the ones a re-parsing implementation would fail: the namespace, the
7
+ // element names, and that nothing about it arrives as a string the browser parsed.
8
+
9
+ import { describe, expect, it } from '@gjsify/unit';
10
+
11
+ import { createCopyGlyph } from './copy-glyph.js';
12
+
13
+ export const CopyGlyphTest = async () => {
14
+ await describe('createCopyGlyph — the copy button still has its icon', async () => {
15
+ await it('is an <svg> in the SVG namespace, sized and viewBox-ed like every other glyph', () => {
16
+ const glyph = createCopyGlyph();
17
+ expect(glyph.namespaceURI).toBe('http://www.w3.org/2000/svg');
18
+ expect(glyph.tagName).toBe('svg');
19
+ expect(glyph.getAttribute('width')).toBe('16');
20
+ expect(glyph.getAttribute('height')).toBe('16');
21
+ expect(glyph.getAttribute('viewBox')).toBe('0 0 16 16');
22
+ // The button owns the accessible name; the glyph is decoration inside it.
23
+ expect(glyph.getAttribute('aria-hidden')).toBe('true');
24
+ expect(glyph.getAttribute('fill')).toBe('currentColor');
25
+ });
26
+
27
+ await it('draws the two sheets as <path> elements, each with a real outline', () => {
28
+ const glyph = createCopyGlyph();
29
+ const paths = [...glyph.children];
30
+ expect(paths.map((el) => el.tagName)).toStrictEqual(['path', 'path']);
31
+ for (const path of paths) {
32
+ expect(path.namespaceURI).toBe('http://www.w3.org/2000/svg');
33
+ expect((path.getAttribute('d') ?? '').length).toBeGreaterThan(10);
34
+ }
35
+ });
36
+
37
+ await it('lands in a button as a drawable glyph', () => {
38
+ const button = document.createElement('button');
39
+ document.body.appendChild(button);
40
+ button.replaceChildren(createCopyGlyph(button.ownerDocument));
41
+ expect(button.querySelector('svg')).not.toBe(null);
42
+ expect(button.getBoundingClientRect().height).toBeGreaterThan(0);
43
+ button.remove();
44
+ });
45
+ });
46
+ };
@@ -0,0 +1,52 @@
1
+ // The copy button's glyph, built as NODES.
2
+ //
3
+ // It was a constant SVG string handed to `innerHTML`, which is safe only until someone
4
+ // edits the string — and it was, next to two `use-markup` surfaces that did the same with
5
+ // AUTHOR-controlled markup, the reason Mozilla's add-on reviewer flagged this package.
6
+ // The glyph is a constant and the sink was not: `createElementNS` costs two lines more and
7
+ // cannot parse anything.
8
+ //
9
+ // It lives in its own module, not in `pango-markup.ts` (which is about Pango markup) and
10
+ // not inline in `adw-source-view.ts` (which would drag CodeMirror into any suite that
11
+ // wants to check the shape) — so a test can assert the glyph without pulling the editor in.
12
+ //
13
+ // The two paths are the two sheets of the `edit-copy` mark: the back sheet offset up-left,
14
+ // the front sheet inset. `aria-hidden` because the button carries the accessible name, the
15
+ // same split `gtk-image.ts` documents.
16
+ //
17
+ // Reference: refs/adwaita-icon-theme/Adwaita/scalable/status-icons/edit-copy-symbolic.svg
18
+ // (the same two-sheet mark, path data rounded to integers here)
19
+ // Copyright (c) 2025 gjsify contributors. MIT License.
20
+
21
+ /** The SVG namespace, spelled once — `createElementNS` needs it on every node. */
22
+ const SVG_NS = 'http://www.w3.org/2000/svg';
23
+
24
+ /** The two sheet outlines, in draw order (back, then front). */
25
+ const SHEETS = [
26
+ 'M4 1a2 2 0 0 0-2 2v7h2V3h6V1H4z',
27
+ 'M7 4a2 2 0 0 0-2 2v7a2 2 0 0 0 2 2h5a2 2 0 0 0 2-2V6a2 2 0 0 0-2-2H7zm0 2h5v7H7V6z',
28
+ ];
29
+
30
+ /** The size both spinners and every symbolic icon in this package draw at. */
31
+ const GLYPH_SIZE = '16';
32
+
33
+ /**
34
+ * A recognisable copy glyph rendered inline so the opt-in `source-view` subpath needs no
35
+ * icon CSS. `doc` is a parameter so a caller outside the main document (a shadow root in
36
+ * another frame) gets nodes its own tree can adopt.
37
+ */
38
+ export function createCopyGlyph(doc: Document = document): SVGSVGElement {
39
+ const svg = doc.createElementNS(SVG_NS, 'svg');
40
+ svg.setAttribute('width', GLYPH_SIZE);
41
+ svg.setAttribute('height', GLYPH_SIZE);
42
+ svg.setAttribute('viewBox', `0 0 ${GLYPH_SIZE} ${GLYPH_SIZE}`);
43
+ svg.setAttribute('fill', 'currentColor');
44
+ svg.setAttribute('aria-hidden', 'true');
45
+
46
+ for (const d of SHEETS) {
47
+ const path = doc.createElementNS(SVG_NS, 'path');
48
+ path.setAttribute('d', d);
49
+ svg.append(path);
50
+ }
51
+ return svg;
52
+ }
@@ -59,6 +59,8 @@ import { GtkAboutDialogTest } from './gtk-about-dialog.spec.js';
59
59
  import { GtkEmojiChooserTest } from './gtk-emoji-chooser.spec.js';
60
60
  import { GtkPrintDialogsTest } from './gtk-print-dialogs.spec.js';
61
61
  import { AdwBannerTest } from './adw-banner.spec.js';
62
+ import { PangoMarkupTest } from './pango-markup.spec.js';
63
+ import { CopyGlyphTest } from './source-view/copy-glyph.spec.js';
62
64
  import { AdwButtonContentTest } from './adw-button-content.spec.js';
63
65
  import { GtkImageTest } from './gtk-image.spec.js';
64
66
  import { AdwIconRegistryTest } from './icon-registry.spec.js';
@@ -191,6 +193,8 @@ run({
191
193
  AdwAboutDialogTest,
192
194
  AdwAlertDialogTest,
193
195
  AdwBannerTest,
196
+ PangoMarkupTest,
197
+ CopyGlyphTest,
194
198
  AdwButtonContentTest,
195
199
  GtkImageTest,
196
200
  AdwIconRegistryTest,