@effect-tui/core 0.12.0 → 0.12.3

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.
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Determine emoji presentation width overrides for a grapheme segment.
3
+ * Returns 2 for emoji presentation, 1 for text presentation, undefined for no override.
4
+ */
5
+ export declare function emojiWidthOverride(segment: string): 1 | 2 | undefined;
6
+ //# sourceMappingURL=emoji.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"emoji.d.ts","sourceRoot":"","sources":["../../src/render/emoji.ts"],"names":[],"mappings":"AAgBA;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,GAAG,CAAC,GAAG,CAAC,GAAG,SAAS,CAiBrE"}
@@ -0,0 +1,36 @@
1
+ import EMOJI_REGEX from "emojibase-regex/emoji";
2
+ import TEXT_REGEX from "emojibase-regex/text";
3
+ const emojiRegex = new RegExp(EMOJI_REGEX.source, EMOJI_REGEX.flags.replace("g", "").replace("y", ""));
4
+ const textRegex = new RegExp(TEXT_REGEX.source, TEXT_REGEX.flags.replace("g", "").replace("y", ""));
5
+ const widthCache = new Map();
6
+ function testRegex(regex, segment) {
7
+ if (regex.global || regex.sticky) {
8
+ regex.lastIndex = 0;
9
+ }
10
+ return regex.test(segment);
11
+ }
12
+ /**
13
+ * Determine emoji presentation width overrides for a grapheme segment.
14
+ * Returns 2 for emoji presentation, 1 for text presentation, undefined for no override.
15
+ */
16
+ export function emojiWidthOverride(segment) {
17
+ const cached = widthCache.get(segment);
18
+ if (cached !== undefined)
19
+ return cached === 0 ? undefined : cached;
20
+ let width = 0;
21
+ if (segment.includes("\uFE0F")) {
22
+ width = 2;
23
+ }
24
+ else if (segment.includes("\uFE0E")) {
25
+ width = 1;
26
+ }
27
+ else if (testRegex(emojiRegex, segment)) {
28
+ width = 2;
29
+ }
30
+ else if (testRegex(textRegex, segment)) {
31
+ width = 1;
32
+ }
33
+ widthCache.set(segment, width);
34
+ return width === 0 ? undefined : width;
35
+ }
36
+ //# sourceMappingURL=emoji.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"emoji.js","sourceRoot":"","sources":["../../src/render/emoji.ts"],"names":[],"mappings":"AAAA,OAAO,WAAW,MAAM,uBAAuB,CAAA;AAC/C,OAAO,UAAU,MAAM,sBAAsB,CAAA;AAI7C,MAAM,UAAU,GAAG,IAAI,MAAM,CAAC,WAAW,CAAC,MAAM,EAAE,WAAW,CAAC,KAAK,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,CAAA;AACtG,MAAM,SAAS,GAAG,IAAI,MAAM,CAAC,UAAU,CAAC,MAAM,EAAE,UAAU,CAAC,KAAK,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,CAAA;AACnG,MAAM,UAAU,GAAG,IAAI,GAAG,EAAsB,CAAA;AAEhD,SAAS,SAAS,CAAC,KAAa,EAAE,OAAe;IAChD,IAAI,KAAK,CAAC,MAAM,IAAI,KAAK,CAAC,MAAM,EAAE,CAAC;QAClC,KAAK,CAAC,SAAS,GAAG,CAAC,CAAA;IACpB,CAAC;IACD,OAAO,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAA;AAC3B,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,kBAAkB,CAAC,OAAe;IACjD,MAAM,MAAM,GAAG,UAAU,CAAC,GAAG,CAAC,OAAO,CAAC,CAAA;IACtC,IAAI,MAAM,KAAK,SAAS;QAAE,OAAO,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM,CAAA;IAElE,IAAI,KAAK,GAAe,CAAC,CAAA;IACzB,IAAI,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC;QAChC,KAAK,GAAG,CAAC,CAAA;IACV,CAAC;SAAM,IAAI,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC;QACvC,KAAK,GAAG,CAAC,CAAA;IACV,CAAC;SAAM,IAAI,SAAS,CAAC,UAAU,EAAE,OAAO,CAAC,EAAE,CAAC;QAC3C,KAAK,GAAG,CAAC,CAAA;IACV,CAAC;SAAM,IAAI,SAAS,CAAC,SAAS,EAAE,OAAO,CAAC,EAAE,CAAC;QAC1C,KAAK,GAAG,CAAC,CAAA;IACV,CAAC;IAED,UAAU,CAAC,GAAG,CAAC,OAAO,EAAE,KAAK,CAAC,CAAA;IAC9B,OAAO,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,KAAK,CAAA;AACvC,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"graphemes.d.ts","sourceRoot":"","sources":["../../src/render/graphemes.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,aAAa,CAAA;AAG1C;;;GAGG;AACH,wBAAiB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,gBAAgB,CAAC,MAAM,CAAC,CAYxE;AAED;;GAEG;AACH,wBAAiB,oBAAoB,CACpC,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,OAAO,GACd,gBAAgB,CAAC;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC,CAUlE"}
1
+ {"version":3,"file":"graphemes.d.ts","sourceRoot":"","sources":["../../src/render/graphemes.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,aAAa,CAAA;AAI1C;;;GAGG;AACH,wBAAiB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,gBAAgB,CAAC,MAAM,CAAC,CAYxE;AAED;;GAEG;AACH,wBAAiB,oBAAoB,CACpC,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,OAAO,GACd,gBAAgB,CAAC;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC,CAQlE"}
@@ -1,3 +1,4 @@
1
+ import { emojiWidthOverride } from "./emoji.js";
1
2
  import { getGraphemeSegmenter } from "./segmenter.js";
2
3
  /**
3
4
  * Iterate grapheme clusters using Intl.Segmenter when available.
@@ -22,10 +23,9 @@ export function* iterateGraphemeCells(text, wcwidth) {
22
23
  for (const segment of iterateGraphemes(text)) {
23
24
  const cp = segment.codePointAt(0) ?? 32;
24
25
  let width = wcwidth(cp) || 1;
25
- // Emoji presentation selector (VS16) should render wide in terminals
26
- if (width === 1 && segment.includes("\uFE0F")) {
27
- width = 2;
28
- }
26
+ const emojiWidth = emojiWidthOverride(segment);
27
+ if (emojiWidth !== undefined)
28
+ width = emojiWidth;
29
29
  yield { segment, cp, width };
30
30
  }
31
31
  }
@@ -1 +1 @@
1
- {"version":3,"file":"graphemes.js","sourceRoot":"","sources":["../../src/render/graphemes.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,oBAAoB,EAAE,MAAM,gBAAgB,CAAA;AAErD;;;GAGG;AACH,MAAM,SAAS,CAAC,CAAC,gBAAgB,CAAC,IAAY;IAC7C,MAAM,GAAG,GAAG,oBAAoB,EAAE,CAAA;IAClC,IAAI,GAAG,EAAE,CAAC;QACT,KAAK,MAAM,EAAE,OAAO,EAAE,IAAI,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC;YAC7C,MAAM,OAAO,CAAA;QACd,CAAC;QACD,OAAM;IACP,CAAC;IAED,KAAK,MAAM,EAAE,IAAI,IAAI,EAAE,CAAC;QACvB,MAAM,EAAE,CAAA;IACT,CAAC;AACF,CAAC;AAED;;GAEG;AACH,MAAM,SAAS,CAAC,CAAC,oBAAoB,CACpC,IAAY,EACZ,OAAgB;IAEhB,KAAK,MAAM,OAAO,IAAI,gBAAgB,CAAC,IAAI,CAAC,EAAE,CAAC;QAC9C,MAAM,EAAE,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC,IAAI,EAAE,CAAA;QACvC,IAAI,KAAK,GAAG,OAAO,CAAC,EAAE,CAAC,IAAI,CAAC,CAAA;QAC5B,qEAAqE;QACrE,IAAI,KAAK,KAAK,CAAC,IAAI,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC;YAC/C,KAAK,GAAG,CAAC,CAAA;QACV,CAAC;QACD,MAAM,EAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,CAAA;IAC7B,CAAC;AACF,CAAC"}
1
+ {"version":3,"file":"graphemes.js","sourceRoot":"","sources":["../../src/render/graphemes.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAA;AAC/C,OAAO,EAAE,oBAAoB,EAAE,MAAM,gBAAgB,CAAA;AAErD;;;GAGG;AACH,MAAM,SAAS,CAAC,CAAC,gBAAgB,CAAC,IAAY;IAC7C,MAAM,GAAG,GAAG,oBAAoB,EAAE,CAAA;IAClC,IAAI,GAAG,EAAE,CAAC;QACT,KAAK,MAAM,EAAE,OAAO,EAAE,IAAI,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC;YAC7C,MAAM,OAAO,CAAA;QACd,CAAC;QACD,OAAM;IACP,CAAC;IAED,KAAK,MAAM,EAAE,IAAI,IAAI,EAAE,CAAC;QACvB,MAAM,EAAE,CAAA;IACT,CAAC;AACF,CAAC;AAED;;GAEG;AACH,MAAM,SAAS,CAAC,CAAC,oBAAoB,CACpC,IAAY,EACZ,OAAgB;IAEhB,KAAK,MAAM,OAAO,IAAI,gBAAgB,CAAC,IAAI,CAAC,EAAE,CAAC;QAC9C,MAAM,EAAE,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC,IAAI,EAAE,CAAA;QACvC,IAAI,KAAK,GAAG,OAAO,CAAC,EAAE,CAAC,IAAI,CAAC,CAAA;QAC5B,MAAM,UAAU,GAAG,kBAAkB,CAAC,OAAO,CAAC,CAAA;QAC9C,IAAI,UAAU,KAAK,SAAS;YAAE,KAAK,GAAG,UAAU,CAAA;QAChD,MAAM,EAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,CAAA;IAC7B,CAAC;AACF,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@effect-tui/core",
3
- "version": "0.12.0",
3
+ "version": "0.12.3",
4
4
  "description": "Terminal UI library built with Effect",
5
5
  "type": "module",
6
6
  "files": [
@@ -44,6 +44,9 @@
44
44
  "format:check": "biome format .",
45
45
  "prepublishOnly": "bun run typecheck && bun run build"
46
46
  },
47
+ "dependencies": {
48
+ "emojibase-regex": "^16.0.0"
49
+ },
47
50
  "peerDependencies": {
48
51
  "effect": "^3.19.13"
49
52
  },
@@ -0,0 +1,38 @@
1
+ import EMOJI_REGEX from "emojibase-regex/emoji"
2
+ import TEXT_REGEX from "emojibase-regex/text"
3
+
4
+ type EmojiWidth = 0 | 1 | 2
5
+
6
+ const emojiRegex = new RegExp(EMOJI_REGEX.source, EMOJI_REGEX.flags.replace("g", "").replace("y", ""))
7
+ const textRegex = new RegExp(TEXT_REGEX.source, TEXT_REGEX.flags.replace("g", "").replace("y", ""))
8
+ const widthCache = new Map<string, EmojiWidth>()
9
+
10
+ function testRegex(regex: RegExp, segment: string): boolean {
11
+ if (regex.global || regex.sticky) {
12
+ regex.lastIndex = 0
13
+ }
14
+ return regex.test(segment)
15
+ }
16
+
17
+ /**
18
+ * Determine emoji presentation width overrides for a grapheme segment.
19
+ * Returns 2 for emoji presentation, 1 for text presentation, undefined for no override.
20
+ */
21
+ export function emojiWidthOverride(segment: string): 1 | 2 | undefined {
22
+ const cached = widthCache.get(segment)
23
+ if (cached !== undefined) return cached === 0 ? undefined : cached
24
+
25
+ let width: EmojiWidth = 0
26
+ if (segment.includes("\uFE0F")) {
27
+ width = 2
28
+ } else if (segment.includes("\uFE0E")) {
29
+ width = 1
30
+ } else if (testRegex(emojiRegex, segment)) {
31
+ width = 2
32
+ } else if (testRegex(textRegex, segment)) {
33
+ width = 1
34
+ }
35
+
36
+ widthCache.set(segment, width)
37
+ return width === 0 ? undefined : width
38
+ }
@@ -1,4 +1,5 @@
1
1
  import type { Wcwidth } from "./buffer.js"
2
+ import { emojiWidthOverride } from "./emoji.js"
2
3
  import { getGraphemeSegmenter } from "./segmenter.js"
3
4
 
4
5
  /**
@@ -29,10 +30,8 @@ export function* iterateGraphemeCells(
29
30
  for (const segment of iterateGraphemes(text)) {
30
31
  const cp = segment.codePointAt(0) ?? 32
31
32
  let width = wcwidth(cp) || 1
32
- // Emoji presentation selector (VS16) should render wide in terminals
33
- if (width === 1 && segment.includes("\uFE0F")) {
34
- width = 2
35
- }
33
+ const emojiWidth = emojiWidthOverride(segment)
34
+ if (emojiWidth !== undefined) width = emojiWidth
36
35
  yield { segment, cp, width }
37
36
  }
38
37
  }
@@ -0,0 +1,9 @@
1
+ declare module "emojibase-regex/emoji" {
2
+ const regex: RegExp
3
+ export default regex
4
+ }
5
+
6
+ declare module "emojibase-regex/text" {
7
+ const regex: RegExp
8
+ export default regex
9
+ }