@suzukihayate/graphemes 0.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Hayate Suzuki (้ˆดๆœจ้ขฏ)
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,43 @@
1
+ # graphemes
2
+
3
+ Emoji- and grapheme-aware string helpers, so `length`, `slice`, `truncate` and `reverse` don't break emoji, flags, skin tones or ZWJ sequences (๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ฆ).
4
+
5
+ In JavaScript, `'a๐Ÿ‘b'.length` is `4`, and naive `slice`/`reverse` can split an emoji in half. `graphemes` counts and cuts by **user-perceived characters** using `Intl.Segmenter` (with a code-point fallback). Zero dependencies.
6
+
7
+ ## Install
8
+ ```bash
9
+ npm install @suzukihayate/graphemes
10
+ ```
11
+
12
+ ## Usage
13
+ ```js
14
+ import { length, slice, truncate, reverse, toGraphemes, at } from '@suzukihayate/graphemes';
15
+
16
+ length('a๐Ÿ‘b'); // 3 (native .length is 4)
17
+ length('๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ฆ'); // 1 (a single ZWJ grapheme)
18
+
19
+ slice('a๐Ÿ‘b๐Ÿ˜€c', 1, 3); // "๐Ÿ‘b"
20
+ reverse('a๐Ÿ‘b'); // "b๐Ÿ‘a" (emoji stays intact)
21
+ truncate('a๐Ÿ‘b๐Ÿ˜€c', 3); // "a๐Ÿ‘โ€ฆ" (no broken emoji)
22
+ at('a๐Ÿ‘b', 1); // "๐Ÿ‘"
23
+
24
+ toGraphemes('a๐Ÿ‘b'); // ["a", "๐Ÿ‘", "b"]
25
+ ```
26
+
27
+ ## API
28
+ - `toGraphemes(str)` โ†’ `string[]`
29
+ - `length(str)` โ†’ grapheme count
30
+ - `slice(str, start?, end?)` โ†’ grapheme-based slice (negative indices ok)
31
+ - `at(str, index)` โ†’ grapheme at index (supports negative)
32
+ - `reverse(str)` โ†’ reverse by graphemes
33
+ - `truncate(str, max, { ellipsis? })` โ†’ truncate to โ‰ค `max` graphemes incl. ellipsis
34
+
35
+ > Uses `Intl.Segmenter` when available (Node 16+, modern browsers). Falls back to code points otherwise.
36
+
37
+ ## Test
38
+ ```bash
39
+ node --test
40
+ ```
41
+
42
+ ## License
43
+ MIT ยฉ 2026 Hayate Suzuki
package/index.d.ts ADDED
@@ -0,0 +1,17 @@
1
+ /** Split a string into grapheme clusters (user-perceived characters). */
2
+ export function toGraphemes(str: string): string[];
3
+ /** Number of grapheme clusters. */
4
+ export function length(str: string): number;
5
+ /** Slice by grapheme index (negative indices count from the end). */
6
+ export function slice(str: string, start?: number, end?: number): string;
7
+ /** Grapheme at the given index (supports negative). */
8
+ export function at(str: string, index: number): string | undefined;
9
+ /** Reverse a string by graphemes. */
10
+ export function reverse(str: string): string;
11
+ /** Truncate to at most `max` graphemes (including the ellipsis). */
12
+ export function truncate(str: string, max: number, opts?: { ellipsis?: string }): string;
13
+ declare const _default: {
14
+ toGraphemes: typeof toGraphemes; length: typeof length; slice: typeof slice;
15
+ at: typeof at; reverse: typeof reverse; truncate: typeof truncate;
16
+ };
17
+ export default _default;
package/index.js ADDED
@@ -0,0 +1,65 @@
1
+ // graphemes โ€” Emoji- and grapheme-aware string helpers, so length/slice/truncate
2
+ // don't break emoji, flags, skin tones or ZWJ sequences (๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ฆ).
3
+ // Uses Intl.Segmenter when available, with a code-point fallback. Zero dependencies.
4
+
5
+ const hasSegmenter = typeof Intl !== 'undefined' && typeof Intl.Segmenter === 'function';
6
+ const segmenter = hasSegmenter ? new Intl.Segmenter(undefined, { granularity: 'grapheme' }) : null;
7
+
8
+ /**
9
+ * Split a string into an array of grapheme clusters (user-perceived characters).
10
+ * @param {string} str
11
+ * @returns {string[]}
12
+ */
13
+ export function toGraphemes(str) {
14
+ const s = String(str);
15
+ if (segmenter) {
16
+ const out = [];
17
+ for (const { segment } of segmenter.segment(s)) out.push(segment);
18
+ return out;
19
+ }
20
+ // Fallback: code points (handles surrogate pairs, but not full ZWJ clusters)
21
+ return Array.from(s);
22
+ }
23
+
24
+ /** Number of grapheme clusters (what a human would count as characters). */
25
+ export function length(str) {
26
+ return toGraphemes(str).length;
27
+ }
28
+
29
+ /**
30
+ * Slice by grapheme index (negative indices count from the end).
31
+ * @param {string} str
32
+ * @param {number} [start]
33
+ * @param {number} [end]
34
+ */
35
+ export function slice(str, start, end) {
36
+ return toGraphemes(str).slice(start, end).join('');
37
+ }
38
+
39
+ /** Grapheme at the given index (supports negative). */
40
+ export function at(str, index) {
41
+ const g = toGraphemes(str);
42
+ const i = index < 0 ? g.length + index : index;
43
+ return g[i];
44
+ }
45
+
46
+ /** Reverse a string by graphemes (keeps emoji/ZWJ sequences intact). */
47
+ export function reverse(str) {
48
+ return toGraphemes(str).reverse().join('');
49
+ }
50
+
51
+ /**
52
+ * Truncate to at most `max` graphemes (including the ellipsis).
53
+ * @param {string} str
54
+ * @param {number} max total grapheme budget
55
+ * @param {{ ellipsis?: string }} [opts]
56
+ */
57
+ export function truncate(str, max, opts = {}) {
58
+ const { ellipsis = 'โ€ฆ' } = opts;
59
+ const g = toGraphemes(str);
60
+ if (g.length <= max) return str;
61
+ const keep = Math.max(0, max - length(ellipsis));
62
+ return g.slice(0, keep).join('') + ellipsis;
63
+ }
64
+
65
+ export default { toGraphemes, length, slice, at, reverse, truncate };
package/package.json ADDED
@@ -0,0 +1,17 @@
1
+ {
2
+ "name": "@suzukihayate/graphemes",
3
+ "version": "0.1.0",
4
+ "description": "Emoji- and grapheme-aware string helpers (length, slice, truncate, reverse) using Intl.Segmenter. Zero dependencies.",
5
+ "type": "module",
6
+ "main": "index.js",
7
+ "types": "index.d.ts",
8
+ "exports": { ".": { "types": "./index.d.ts", "default": "./index.js" } },
9
+ "files": ["index.js", "index.d.ts", "README.md", "LICENSE"],
10
+ "scripts": { "test": "node --test" },
11
+ "keywords": ["grapheme", "emoji", "unicode", "string", "length", "truncate", "slice", "segmenter", "zero-dependency"],
12
+ "repository": { "type": "git", "url": "git+https://github.com/HaYaTedonn/graphemes.git" },
13
+ "author": "Hayate Suzuki",
14
+ "license": "MIT",
15
+ "engines": { "node": ">=18" },
16
+ "sideEffects": false
17
+ }