@iyulab/editor-components 0.6.3 → 0.7.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,31 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.7.0] - 2026-10-06
4
+
5
+ ### Added
6
+
7
+ - **`u-code-editor` tells a screen reader the way out.** While editable, Tab indents and `Ctrl+M` (macOS
8
+ `Ctrl+Shift+M`) switches it to moving the focus; that method is now the editing area's accessible description,
9
+ announced on entering — WCAG 2.1.2 allows a non-standard exit when the user is told it, and 0.6.4 left the telling
10
+ to each page. Localized through `Locale` (`editorLocale`, English and Korean built in). Read-only has no hint (Tab
11
+ already leaves there). `no-tab-hint` drops it for a page that states the method itself.
12
+
13
+ ### Fixed
14
+
15
+ - **The editing area is named by `label`.** It was always "Editor content", whatever the header said; a label change
16
+ now renames it too.
17
+ - **Changing `readOnly` after the editor was created takes effect.** It was only read at creation.
18
+
19
+ ### Changed
20
+
21
+ - Requires `@iyulab/components` `>=1.23.0` (`Locale.namespace`).
22
+
23
+ ## [0.6.4] - 2026-10-06
24
+
25
+ ### Documentation
26
+
27
+ - **Keyboard exit** (WCAG 2.1.2) is now stated and tested: in `u-text-editor` Tab moves on to the next control; in `u-code-editor` Tab indents and **`Ctrl+M`** (macOS: `Ctrl+Shift+M`) switches Tab to moving the focus — the method a screen hosting a code editor should tell its users. README accessibility table and the skill references (`code-editor.md` · `text-editor.md`).
28
+
3
29
  ## [0.6.3] - 2026-10-06
4
30
 
5
31
  ### Documentation
package/README.md CHANGED
@@ -131,6 +131,7 @@ Quill.js 기반 리치 텍스트(WYSIWYG) 에디터 컴포넌트입니다.
131
131
  | 성공 기준 | 보장 | 어디서 재는가 |
132
132
  |---|---|---|
133
133
  | SC 2.5.8 타깃 크기(최소) | `u-text-editor` 의 포인터 타깃이 24×24 CSS px 이상이거나 간격 예외(중심 간 24px)를 충족하고, 그 좌표에서 실제로 눌린다 — Quill 이 그리는 툴바(선택기가 열린 상태·색 견본·링크 툴팁 포함)도 잰다 | `tests/browser/target-size.browser.test.ts`(실제 크로미움) |
134
+ | SC 2.1.2 키보드 함정 없음 | `u-text-editor` 안의 `Tab` 은 다음 컨트롤로 나간다(탭 문자를 넣지 않는다). `u-code-editor` 안의 `Tab` 은 들여쓰기이고, **`Ctrl+M`(macOS: `Ctrl+Shift+M`)** 이 Monaco 의 «Tab 으로 포커스 이동» 을 켜고 끈다 — 그 뒤의 `Tab` 은 편집기를 떠난다. 편집 가능한 동안 편집 영역의 접근성 설명이 이 방법을 말한다(로캘 · 읽기 전용에서는 Tab 이 이미 나가므로 없음 · `no-tab-hint` 로 끔). 편집 영역의 이름은 `label` | `tests/browser/keyboard-exit.browser.test.ts` |
134
135
  | SC 2.1.1 키보드(포인터 커서 검사) | 포인터 커서를 보이면서 상호작용 요소가 아닌 것이 없다 — Quill 이 `href` 없는 앵커로 그리는 링크 툴팁의 편집·제거도 버튼 역할로 포커스를 받고 Enter/Space 로 눌린다 | `tests/browser/target-size.browser.test.ts` · `tests/browser/text-editor-link-tooltip-keyboard.browser.test.ts` |
135
136
 
136
137
  `u-code-editor` 는 스스로 렌더하는 조작부가 없어(액션은 소비자가 `header-actions` 슬롯으로 넣는다) 이 게이트의 대상이 아닙니다 — 슬롯에 넣는 버튼의 크기는 넣는 쪽이 정합니다.
@@ -9,8 +9,14 @@ export declare class UCodeEditor extends UElement {
9
9
  static styles: import('lit').CSSResultGroup[];
10
10
  /** Specifies whether the header should be displayed or not. @default false */
11
11
  headless: boolean;
12
- /** The label text displayed in the header of the code editor. @default "Editor" */
12
+ /** The label text displayed in the header of the code editor — also the editing area's accessible name. @default "Editor" */
13
13
  label: string;
14
+ /**
15
+ * Do not attach the Tab hint. While editable, Tab indents and `Ctrl+M` (macOS `Ctrl+Shift+M`) switches it to moving
16
+ * the focus; the editing area carries that as its accessible description, so a screen reader user hears the way
17
+ * out when entering (WCAG 2.1.2). Set this when the page states the method itself. @default false
18
+ */
19
+ noTabHint: boolean;
14
20
  /** 편집기 테마. 문서 테마를 따라 자동으로 동기화된다(직접 지정해도 덮어쓰인다). */
15
21
  theme: "light" | "dark";
16
22
  /** Whether the editor should be in read-only mode, preventing user input. @default false */
@@ -31,6 +37,12 @@ export declare class UCodeEditor extends UElement {
31
37
  disconnectedCallback(): void;
32
38
  protected firstUpdated(changedProperties: PropertyValues): Promise<void>;
33
39
  protected updated(changedProperties: PropertyValues): Promise<void>;
40
+ /**
41
+ * The Tab hint as the editing area's description — only while editable: read-only, Monaco lets Tab leave the
42
+ * editor, so there is nothing to explain. The focusable node is Monaco's own (an EditContext `div` with
43
+ * `role="textbox"` where the browser supports it, a `textarea` elsewhere), so the description is set on it.
44
+ */
45
+ private describeInput;
34
46
  render(): import('lit-html').TemplateResult<1>;
35
47
  }
36
48
  declare global {
@@ -1,6 +1,7 @@
1
1
  import "./UCodeEditor.worker.js";
2
2
  import _virtual_monaco_structure_css_default from "../../_virtual/_virtual_monaco-structure-css.js";
3
3
  import { styles } from "./UCodeEditor.styles.js";
4
+ import { editorLocale } from "../../locale.js";
4
5
  import __decorateMetadata from "../../_virtual/_@oxc-project_runtime@0.152.0/helpers/esm/decorateMetadata.js";
5
6
  import __decorate from "../../_virtual/_@oxc-project_runtime@0.152.0/helpers/esm/decorate.js";
6
7
  import { html, unsafeCSS } from "lit";
@@ -15,6 +16,7 @@ var UCodeEditor = class UCodeEditor extends UElement {
15
16
  super(..._args);
16
17
  this.headless = false;
17
18
  this.label = "Editor";
19
+ this.noTabHint = false;
18
20
  this.theme = "light";
19
21
  this.readOnly = false;
20
22
  this.language = "json";
@@ -58,10 +60,12 @@ var UCodeEditor = class UCodeEditor extends UElement {
58
60
  lineNumbersMinChars: 2,
59
61
  lineDecorationsWidth: 1,
60
62
  readOnly: this.readOnly,
63
+ ariaLabel: this.label,
61
64
  value: this.value,
62
65
  scrollBeyondLastLine: false,
63
66
  scrollbar: { alwaysConsumeMouseWheel: false }
64
67
  });
68
+ this.describeInput();
65
69
  this.editor.onDidChangeModelContent(() => {
66
70
  if (this.syncingValue) return;
67
71
  this.value = this.editor.getValue();
@@ -80,6 +84,20 @@ var UCodeEditor = class UCodeEditor extends UElement {
80
84
  }
81
85
  if (changedProperties.has("theme") && this.editor) this.editor.updateOptions({ theme: this.theme === "light" ? "vs-light" : "vs-dark" });
82
86
  if (changedProperties.has("language") && this.editor) monaco.editor.setModelLanguage(this.editor.getModel(), this.language);
87
+ if (changedProperties.has("label") && this.editor) this.editor.updateOptions({ ariaLabel: this.label });
88
+ if (changedProperties.has("readOnly") && this.editor) this.editor.updateOptions({ readOnly: this.readOnly });
89
+ if ((changedProperties.has("noTabHint") || changedProperties.has("readOnly")) && this.editor) this.describeInput();
90
+ }
91
+ /**
92
+ * The Tab hint as the editing area's description — only while editable: read-only, Monaco lets Tab leave the
93
+ * editor, so there is nothing to explain. The focusable node is Monaco's own (an EditContext `div` with
94
+ * `role="textbox"` where the browser supports it, a `textarea` elsewhere), so the description is set on it.
95
+ */
96
+ describeInput() {
97
+ const input = this.container.value?.querySelector("[role=\"textbox\"], textarea");
98
+ if (!input) return;
99
+ if (this.noTabHint || this.readOnly) input.removeAttribute("aria-describedby");
100
+ else input.setAttribute("aria-describedby", "tab-hint");
83
101
  }
84
102
  render() {
85
103
  return html`
@@ -91,6 +109,7 @@ var UCodeEditor = class UCodeEditor extends UElement {
91
109
  <div class="editor">
92
110
  <main ${ref(this.container)}></main>
93
111
  </div>
112
+ <span id="tab-hint" hidden>${editorLocale.text("tabFocusHint")}</span>
94
113
  `;
95
114
  }
96
115
  };
@@ -99,6 +118,10 @@ __decorate([property({
99
118
  reflect: true
100
119
  }), __decorateMetadata("design:type", Boolean)], UCodeEditor.prototype, "headless", void 0);
101
120
  __decorate([property({ type: String }), __decorateMetadata("design:type", String)], UCodeEditor.prototype, "label", void 0);
121
+ __decorate([property({
122
+ type: Boolean,
123
+ attribute: "no-tab-hint"
124
+ }), __decorateMetadata("design:type", Boolean)], UCodeEditor.prototype, "noTabHint", void 0);
102
125
  __decorate([property({ type: String }), __decorateMetadata("design:type", String)], UCodeEditor.prototype, "theme", void 0);
103
126
  __decorate([property({ type: Boolean }), __decorateMetadata("design:type", Boolean)], UCodeEditor.prototype, "readOnly", void 0);
104
127
  __decorate([property({ type: String }), __decorateMetadata("design:type", String)], UCodeEditor.prototype, "language", void 0);
package/dist/index.d.ts CHANGED
@@ -1,2 +1,3 @@
1
1
  export * from './components/code-editor/UCodeEditor.js';
2
2
  export * from './components/text-editor/UTextEditor.js';
3
+ export * from './locale.js';
package/dist/index.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { editorLocale } from "./locale.js";
1
2
  import { UCodeEditor } from "./components/code-editor/UCodeEditor.js";
2
3
  import { UTextEditor } from "./components/text-editor/UTextEditor.js";
3
- export { UCodeEditor, UTextEditor };
4
+ export { UCodeEditor, UTextEditor, editorLocale };
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Strings `@iyulab/editor-components` draws itself — registered in the shared `Locale` chain of
3
+ * `@iyulab/components`, so `Locale.set('ko')` reaches them. English and Korean are built in; add others with
4
+ * `editorLocale.register('<tag>', { … })`.
5
+ */
6
+ export type EditorMessageKey = 'tabFocusHint';
7
+ export declare const editorLocale: import('@iyulab/components/dist/utilities/Locale.js').LocaleNamespace<"tabFocusHint">;
package/dist/locale.js ADDED
@@ -0,0 +1,7 @@
1
+ import { Locale } from "@iyulab/components/dist/utilities/Locale.js";
2
+ //#region src/locale.ts
3
+ var editorLocale = Locale.namespace("editor-components");
4
+ editorLocale.register("en", { tabFocusHint: "Tab indents. To move the focus out with Tab, press Ctrl+M first (Ctrl+Shift+M on macOS)." });
5
+ editorLocale.register("ko", { tabFocusHint: "Tab 은 들여쓰기입니다. Tab 으로 편집기를 벗어나려면 먼저 Ctrl+M(macOS 는 Ctrl+Shift+M)을 누르세요." });
6
+ //#endregion
7
+ export { editorLocale };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@iyulab/editor-components",
3
3
  "description": "various editor components by iyulab",
4
- "version": "0.6.3",
4
+ "version": "0.7.0",
5
5
  "keywords": [
6
6
  "iyulab",
7
7
  "web-components",
@@ -33,7 +33,9 @@
33
33
  "./dist/components/*/U*.js",
34
34
  "./src/components/*/U*.ts",
35
35
  "./dist/index.js",
36
- "./src/index.ts"
36
+ "./src/index.ts",
37
+ "./dist/locale.js",
38
+ "./src/locale.ts"
37
39
  ],
38
40
  "exports": {
39
41
  ".": {
@@ -63,7 +65,7 @@
63
65
  "quill": "^2.0.3"
64
66
  },
65
67
  "peerDependencies": {
66
- "@iyulab/components": ">=1.15.0",
68
+ "@iyulab/components": ">=1.23.0",
67
69
  "@lit/react": "^1.0.8",
68
70
  "react": "^18.0.0 || ^19.0.0",
69
71
  "monaco-editor": ">=0.56.0 <0.58.0"
@@ -22,9 +22,10 @@ Monaco Editor wrapped as a custom element. Syntax highlighting, per-language con
22
22
  | Property | Type | Default | Reflect | Description |
23
23
  |----------|------|---------|---------|-------------|
24
24
  | `headless` | `boolean` | `false` | ✓ | Hides the header (label + `header-actions` slot) |
25
- | `label` | `string` | `'Editor'` | — | Header title text |
25
+ | `label` | `string` | `'Editor'` | — | Header title text — also the editing area's accessible name |
26
26
  | `theme` | `'light' \| 'dark'` | `'light'` | — | Editor color theme. Synced automatically from `Theme.resolved()` on connect and on every `data-theme`/`theme` attribute mutation on `document.documentElement` — the declared default is overwritten before first render, and setting it directly is overwritten by the next sync |
27
- | `readOnly` | `boolean` | `false` | — | Prevents user input |
27
+ | `readOnly` | `boolean` | `false` | — | Prevents user input (applies when changed after creation too) |
28
+ | `noTabHint` | `boolean` | `false` | — | Attribute `no-tab-hint`. Drops the Tab hint from the editing area's description (see Keyboard) — for a page that states the method itself |
28
29
  | `language` | `string` | `'json'` | — | Monaco language id (`"javascript"`, `"typescript"`, …) |
29
30
  | `fontSize` | `number` | `14` | — | Editor font size in pixels |
30
31
  | `value` | `string` | `''` | — | Current text content |
@@ -64,6 +65,17 @@ other unchanged.
64
65
  |-------|--------|--------------|
65
66
  | `change` | none (plain `Event`) | Fired on user edits (`Monaco.onDidChangeModelContent`). Not fired for programmatic `value` assignment while Monaco already holds that value. Read the new content from `value` |
66
67
 
68
+ ## Keyboard
69
+
70
+ `Tab` inside the editor indents — a code editor needs it — so `Tab` does not move the focus on its own.
71
+ **`Ctrl+M`** (macOS: `Ctrl+Shift+M`) toggles Monaco's "Tab moves focus" mode; after it, `Tab` / `Shift+Tab`
72
+ leave the editor. That is the keyboard way out (WCAG 2.1.2 allows a non-standard exit when the user is told
73
+ the method), and the editor tells it: while editable, the editing area's accessible description says so (localized
74
+ through `Locale` — English and Korean built in, others via `editorLocale.register`), so a screen reader announces it on
75
+ entering. Read-only, `Tab` already leaves and there is no hint. A page may still show the method visibly — the hint
76
+ is for assistive technology, not on screen.
77
+ `Tab` from the control before the editor enters it.
78
+
67
79
  ## CSS Custom Properties
68
80
 
69
81
  None. Layout is plain flexbox — the header keeps its own height and the editing area takes the rest — so there is no layout variable to override (see [Sizing](#sizing) for how the element is sized), and colours come from Monaco's own theme (`vs-light`/`vs-dark`) rather than this library's `--u-*` design tokens.
@@ -78,6 +78,11 @@ with 0.5.0 — see the CHANGELOG.
78
78
  |-------|--------|--------------|
79
79
  | `change` | `{ html: string, text: string, delta: QuillDelta }` | Fired only for user edits (Quill `source === 'user'`) — composed so it crosses the shadow boundary |
80
80
 
81
+ ## Keyboard
82
+
83
+ `Tab` inside the editor moves on to the next control — it does not insert a tab character, so the
84
+ keyboard is never trapped in the editor (WCAG 2.1.2).
85
+
81
86
  ## CSS Custom Properties
82
87
 
83
88
  `UTextEditor` doesn't declare any custom properties of its own — its styles read the shared