@tldraw/utils 5.4.0-canary.fa89ff126c46 → 5.4.0-next.038cf4b0c054

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.
@@ -795,16 +795,6 @@ export declare function isNonNull<T>(value: T): value is typeof value extends nu
795
795
  */
796
796
  export declare function isNonNullish<T>(value: T): value is typeof value extends undefined ? never : typeof value extends null ? never : T;
797
797
 
798
- /**
799
- * Iterate over the grapheme clusters of a string — user-perceived characters such as emoji
800
- * sequences, flags, and accented letters — instead of UTF-16 code units. Uses `Intl.Segmenter`
801
- * where available and falls back to iterating by code point on browsers that lack it, where a
802
- * multi-code-point cluster is yielded as its component code points rather than as one segment.
803
- *
804
- * @public
805
- */
806
- export declare function iterateGraphemes(str: string): IterableIterator<string>;
807
-
808
798
  /**
809
799
  * A type representing a JSON array containing any valid JSON values.
810
800
  * Arrays can contain mixed types of JSON values including nested arrays and objects.
package/dist-cjs/index.js CHANGED
@@ -89,7 +89,6 @@ __export(index_exports, {
89
89
  isNativeStructuredClone: () => import_value.isNativeStructuredClone,
90
90
  isNonNull: () => import_value.isNonNull,
91
91
  isNonNullish: () => import_value.isNonNullish,
92
- iterateGraphemes: () => import_string.iterateGraphemes,
93
92
  last: () => import_array.last,
94
93
  lerp: () => import_number.lerp,
95
94
  lns: () => import_hash.lns,
@@ -174,7 +173,7 @@ var import_version2 = require("./lib/version");
174
173
  var import_warn = require("./lib/warn");
175
174
  (0, import_version.registerTldrawLibraryVersion)(
176
175
  "@tldraw/utils",
177
- "5.4.0-canary.fa89ff126c46",
176
+ "5.4.0-next.038cf4b0c054",
178
177
  "cjs"
179
178
  );
180
179
  //# sourceMappingURL=index.js.map
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/index.ts"],
4
- "sourcesContent": ["import { registerTldrawLibraryVersion } from './lib/version'\n\nexport { default as isEqual } from 'lodash.isequal'\nexport { default as isEqualWith } from 'lodash.isequalwith'\nexport { default as throttle } from 'lodash.throttle'\nexport { default as uniq } from 'lodash.uniq'\nexport {\n\tareArraysShallowEqual,\n\tcompact,\n\tdedupe,\n\tlast,\n\tmaxBy,\n\tmergeArraysAndReplaceDefaults,\n\tminBy,\n\tpartition,\n\trotateArray,\n} from './lib/array'\nexport { bind } from './lib/bind'\nexport { WeakCache } from './lib/cache'\nexport {\n\tassert,\n\tassertExists,\n\texhaustiveSwitchError,\n\tpromiseWithResolve,\n\tResult,\n\tsleep,\n\ttype ErrorResult,\n\ttype OkResult,\n} from './lib/control'\nexport { debounce } from './lib/debounce'\nexport { annotateError, getErrorAnnotations, type ErrorAnnotations } from './lib/error'\nexport { ExecutionQueue } from './lib/ExecutionQueue'\nexport { FileHelpers } from './lib/file'\nexport { noop, omitFromStackTrace } from './lib/function'\nexport { getHashForBuffer, getHashForObject, getHashForString, lns } from './lib/hash'\nexport { mockUniqueId, restoreUniqueId, uniqueId } from './lib/id'\nexport { getFirstFromIterable } from './lib/iterable'\nexport { LruCache } from './lib/LruCache'\nexport type { JsonArray, JsonObject, JsonPrimitive, JsonValue } from './lib/json-value'\nexport {\n\tDEFAULT_SUPPORT_VIDEO_TYPES,\n\tDEFAULT_SUPPORTED_IMAGE_TYPES,\n\tDEFAULT_SUPPORTED_MEDIA_TYPE_LIST,\n\tDEFAULT_SUPPORTED_MEDIA_TYPES,\n\tMediaHelpers,\n} from './lib/media/media'\nexport { PngHelpers } from './lib/media/png'\nexport { fetch, Image } from './lib/network'\nexport { invLerp, lerp, modulate, rng } from './lib/number'\nexport {\n\tareObjectsShallowEqual,\n\tfilterEntries,\n\tgetChangedKeys,\n\tgetOwnProperty,\n\tgroupBy,\n\thasOwnProperty,\n\tisEqualAllowingForFloatingPointErrors,\n\tmapObjectMapValues,\n\tobjectMapEntries,\n\tobjectMapEntriesIterable,\n\tobjectMapFromEntries,\n\tobjectMapKeys,\n\tobjectMapValues,\n\tomit,\n} from './lib/object'\nexport { measureAverageDuration, measureCbDuration, measureDuration } from './lib/perf'\nexport { PerformanceTracker } from './lib/PerformanceTracker'\nexport {\n\tgetIndexAbove,\n\tgetIndexBelow,\n\tgetIndexBetween,\n\tgetIndices,\n\tgetIndicesAbove,\n\tgetIndicesBelow,\n\tgetIndicesBetween,\n\tsortByIndex,\n\tsortByMaybeIndex,\n\tvalidateIndexKey,\n\tZERO_INDEX_KEY,\n\ttype IndexKey,\n} from './lib/reordering'\nexport { retry } from './lib/retry'\nexport { sortById } from './lib/sort'\nexport {\n\tclearLocalStorage,\n\tclearSessionStorage,\n\tdeleteFromLocalStorage,\n\tdeleteFromSessionStorage,\n\tgetFromLocalStorage,\n\tgetFromSessionStorage,\n\tsetInLocalStorage,\n\tsetInSessionStorage,\n} from './lib/storage'\nexport { getFirstCharacter, iterateGraphemes } from './lib/string'\nexport { stringEnum } from './lib/stringEnum'\nexport { FpsScheduler, fpsThrottle, throttleToNextFrame } from './lib/throttle'\nexport { Timers } from './lib/timers'\nexport {\n\ttype Awaitable,\n\ttype Expand,\n\ttype MakeUndefinedOptional,\n\ttype RecursivePartial,\n\ttype Required,\n} from './lib/types'\nexport { safeParseUrl } from './lib/url'\nexport {\n\tisDefined,\n\tisNativeStructuredClone,\n\tisNonNull,\n\tisNonNullish,\n\tSTRUCTURED_CLONE_OBJECT_PROTOTYPE,\n\tstructuredClone,\n} from './lib/value'\nexport { registerTldrawLibraryVersion } from './lib/version'\nexport { warnDeprecatedGetter, warnOnce } from './lib/warn'\n\nregisterTldrawLibraryVersion(\n\t(globalThis as any).TLDRAW_LIBRARY_NAME,\n\t(globalThis as any).TLDRAW_LIBRARY_VERSION,\n\t(globalThis as any).TLDRAW_LIBRARY_MODULES\n)\n"],
5
- "mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,qBAA6C;AAE7C,oBAAmC;AACnC,IAAAA,iBAAuC;AACvC,IAAAA,iBAAoC;AACpC,IAAAA,iBAAgC;AAChC,mBAUO;AACP,kBAAqB;AACrB,mBAA0B;AAC1B,qBASO;AACP,sBAAyB;AACzB,mBAA0E;AAC1E,4BAA+B;AAC/B,kBAA4B;AAC5B,sBAAyC;AACzC,kBAA0E;AAC1E,gBAAwD;AACxD,sBAAqC;AACrC,sBAAyB;AAEzB,mBAMO;AACP,iBAA2B;AAC3B,qBAA6B;AAC7B,oBAA6C;AAC7C,oBAeO;AACP,kBAA2E;AAC3E,gCAAmC;AACnC,wBAaO;AACP,mBAAsB;AACtB,kBAAyB;AACzB,qBASO;AACP,oBAAoD;AACpD,wBAA2B;AAC3B,sBAA+D;AAC/D,oBAAuB;AAQvB,iBAA6B;AAC7B,mBAOO;AACP,IAAAC,kBAA6C;AAC7C,kBAA+C;AAAA,IAE/C;AAAA,EACE;AAAA,EACA;AAAA,EACA;AACF;",
4
+ "sourcesContent": ["import { registerTldrawLibraryVersion } from './lib/version'\n\nexport { default as isEqual } from 'lodash.isequal'\nexport { default as isEqualWith } from 'lodash.isequalwith'\nexport { default as throttle } from 'lodash.throttle'\nexport { default as uniq } from 'lodash.uniq'\nexport {\n\tareArraysShallowEqual,\n\tcompact,\n\tdedupe,\n\tlast,\n\tmaxBy,\n\tmergeArraysAndReplaceDefaults,\n\tminBy,\n\tpartition,\n\trotateArray,\n} from './lib/array'\nexport { bind } from './lib/bind'\nexport { WeakCache } from './lib/cache'\nexport {\n\tassert,\n\tassertExists,\n\texhaustiveSwitchError,\n\tpromiseWithResolve,\n\tResult,\n\tsleep,\n\ttype ErrorResult,\n\ttype OkResult,\n} from './lib/control'\nexport { debounce } from './lib/debounce'\nexport { annotateError, getErrorAnnotations, type ErrorAnnotations } from './lib/error'\nexport { ExecutionQueue } from './lib/ExecutionQueue'\nexport { FileHelpers } from './lib/file'\nexport { noop, omitFromStackTrace } from './lib/function'\nexport { getHashForBuffer, getHashForObject, getHashForString, lns } from './lib/hash'\nexport { mockUniqueId, restoreUniqueId, uniqueId } from './lib/id'\nexport { getFirstFromIterable } from './lib/iterable'\nexport { LruCache } from './lib/LruCache'\nexport type { JsonArray, JsonObject, JsonPrimitive, JsonValue } from './lib/json-value'\nexport {\n\tDEFAULT_SUPPORT_VIDEO_TYPES,\n\tDEFAULT_SUPPORTED_IMAGE_TYPES,\n\tDEFAULT_SUPPORTED_MEDIA_TYPE_LIST,\n\tDEFAULT_SUPPORTED_MEDIA_TYPES,\n\tMediaHelpers,\n} from './lib/media/media'\nexport { PngHelpers } from './lib/media/png'\nexport { fetch, Image } from './lib/network'\nexport { invLerp, lerp, modulate, rng } from './lib/number'\nexport {\n\tareObjectsShallowEqual,\n\tfilterEntries,\n\tgetChangedKeys,\n\tgetOwnProperty,\n\tgroupBy,\n\thasOwnProperty,\n\tisEqualAllowingForFloatingPointErrors,\n\tmapObjectMapValues,\n\tobjectMapEntries,\n\tobjectMapEntriesIterable,\n\tobjectMapFromEntries,\n\tobjectMapKeys,\n\tobjectMapValues,\n\tomit,\n} from './lib/object'\nexport { measureAverageDuration, measureCbDuration, measureDuration } from './lib/perf'\nexport { PerformanceTracker } from './lib/PerformanceTracker'\nexport {\n\tgetIndexAbove,\n\tgetIndexBelow,\n\tgetIndexBetween,\n\tgetIndices,\n\tgetIndicesAbove,\n\tgetIndicesBelow,\n\tgetIndicesBetween,\n\tsortByIndex,\n\tsortByMaybeIndex,\n\tvalidateIndexKey,\n\tZERO_INDEX_KEY,\n\ttype IndexKey,\n} from './lib/reordering'\nexport { retry } from './lib/retry'\nexport { sortById } from './lib/sort'\nexport {\n\tclearLocalStorage,\n\tclearSessionStorage,\n\tdeleteFromLocalStorage,\n\tdeleteFromSessionStorage,\n\tgetFromLocalStorage,\n\tgetFromSessionStorage,\n\tsetInLocalStorage,\n\tsetInSessionStorage,\n} from './lib/storage'\nexport { getFirstCharacter } from './lib/string'\nexport { stringEnum } from './lib/stringEnum'\nexport { FpsScheduler, fpsThrottle, throttleToNextFrame } from './lib/throttle'\nexport { Timers } from './lib/timers'\nexport {\n\ttype Awaitable,\n\ttype Expand,\n\ttype MakeUndefinedOptional,\n\ttype RecursivePartial,\n\ttype Required,\n} from './lib/types'\nexport { safeParseUrl } from './lib/url'\nexport {\n\tisDefined,\n\tisNativeStructuredClone,\n\tisNonNull,\n\tisNonNullish,\n\tSTRUCTURED_CLONE_OBJECT_PROTOTYPE,\n\tstructuredClone,\n} from './lib/value'\nexport { registerTldrawLibraryVersion } from './lib/version'\nexport { warnDeprecatedGetter, warnOnce } from './lib/warn'\n\nregisterTldrawLibraryVersion(\n\t(globalThis as any).TLDRAW_LIBRARY_NAME,\n\t(globalThis as any).TLDRAW_LIBRARY_VERSION,\n\t(globalThis as any).TLDRAW_LIBRARY_MODULES\n)\n"],
5
+ "mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,qBAA6C;AAE7C,oBAAmC;AACnC,IAAAA,iBAAuC;AACvC,IAAAA,iBAAoC;AACpC,IAAAA,iBAAgC;AAChC,mBAUO;AACP,kBAAqB;AACrB,mBAA0B;AAC1B,qBASO;AACP,sBAAyB;AACzB,mBAA0E;AAC1E,4BAA+B;AAC/B,kBAA4B;AAC5B,sBAAyC;AACzC,kBAA0E;AAC1E,gBAAwD;AACxD,sBAAqC;AACrC,sBAAyB;AAEzB,mBAMO;AACP,iBAA2B;AAC3B,qBAA6B;AAC7B,oBAA6C;AAC7C,oBAeO;AACP,kBAA2E;AAC3E,gCAAmC;AACnC,wBAaO;AACP,mBAAsB;AACtB,kBAAyB;AACzB,qBASO;AACP,oBAAkC;AAClC,wBAA2B;AAC3B,sBAA+D;AAC/D,oBAAuB;AAQvB,iBAA6B;AAC7B,mBAOO;AACP,IAAAC,kBAA6C;AAC7C,kBAA+C;AAAA,IAE/C;AAAA,EACE;AAAA,EACA;AAAA,EACA;AACF;",
6
6
  "names": ["import_lodash", "import_version"]
7
7
  }
@@ -18,32 +18,14 @@ var __copyProps = (to, from, except, desc) => {
18
18
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
19
  var string_exports = {};
20
20
  __export(string_exports, {
21
- getFirstCharacter: () => getFirstCharacter,
22
- iterateGraphemes: () => iterateGraphemes
21
+ getFirstCharacter: () => getFirstCharacter
23
22
  });
24
23
  module.exports = __toCommonJS(string_exports);
25
24
  let graphemeSegmenter;
26
- let checkedForSegmenter = false;
27
- function getGraphemeSegmenter() {
28
- if (!checkedForSegmenter) {
29
- checkedForSegmenter = true;
30
- if (typeof Intl !== "undefined" && "Segmenter" in Intl) {
31
- graphemeSegmenter = new Intl.Segmenter(void 0, { granularity: "grapheme" });
32
- }
33
- }
34
- return graphemeSegmenter;
35
- }
36
- function* iterateGraphemes(str) {
37
- const segmenter = getGraphemeSegmenter();
38
- if (segmenter) {
39
- for (const { segment } of segmenter.segment(str)) yield segment;
40
- } else {
41
- yield* str;
42
- }
43
- }
44
25
  function getFirstCharacter(str) {
45
26
  if (!str) return "";
46
- for (const segment of iterateGraphemes(str)) {
27
+ graphemeSegmenter ??= new Intl.Segmenter(void 0, { granularity: "grapheme" });
28
+ for (const { segment } of graphemeSegmenter.segment(str)) {
47
29
  return segment;
48
30
  }
49
31
  return "";
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/lib/string.ts"],
4
- "sourcesContent": ["let graphemeSegmenter: Intl.Segmenter | undefined\nlet checkedForSegmenter = false\n\n/**\n * Resolve a shared grapheme `Intl.Segmenter`, or `undefined` when the runtime has no\n * `Intl.Segmenter`. `Intl.Segmenter` is unavailable on some older browsers (notably Firefox before\n * 125), so feature-detect it once rather than constructing it eagerly, which would throw at module\n * load and take down the whole app.\n */\nfunction getGraphemeSegmenter(): Intl.Segmenter | undefined {\n\tif (!checkedForSegmenter) {\n\t\tcheckedForSegmenter = true\n\t\tif (typeof Intl !== 'undefined' && 'Segmenter' in Intl) {\n\t\t\tgraphemeSegmenter = new Intl.Segmenter(undefined, { granularity: 'grapheme' })\n\t\t}\n\t}\n\treturn graphemeSegmenter\n}\n\n/**\n * Iterate over the grapheme clusters of a string \u2014 user-perceived characters such as emoji\n * sequences, flags, and accented letters \u2014 instead of UTF-16 code units. Uses `Intl.Segmenter`\n * where available and falls back to iterating by code point on browsers that lack it, where a\n * multi-code-point cluster is yielded as its component code points rather than as one segment.\n *\n * @public\n */\nexport function* iterateGraphemes(str: string): IterableIterator<string> {\n\tconst segmenter = getGraphemeSegmenter()\n\tif (segmenter) {\n\t\tfor (const { segment } of segmenter.segment(str)) yield segment\n\t} else {\n\t\tyield* str\n\t}\n}\n\n/**\n * Get the first character of a string, treating a multi-code-unit character such as an emoji as a\n * single character. Unlike `str[0]` or `str.charAt(0)`, which return one UTF-16 code unit and so\n * split an emoji into a broken half, this returns the first whole grapheme cluster. Returns an\n * empty string when the input is empty.\n *\n * @example\n * ```ts\n * getFirstCharacter('hello') // 'h'\n * getFirstCharacter('\uD83D\uDE00 hello') // '\uD83D\uDE00'\n * getFirstCharacter('') // ''\n * ```\n *\n * @public\n */\nexport function getFirstCharacter(str: string): string {\n\tif (!str) return ''\n\tfor (const segment of iterateGraphemes(str)) {\n\t\treturn segment\n\t}\n\treturn ''\n}\n"],
5
- "mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAAI;AACJ,IAAI,sBAAsB;AAQ1B,SAAS,uBAAmD;AAC3D,MAAI,CAAC,qBAAqB;AACzB,0BAAsB;AACtB,QAAI,OAAO,SAAS,eAAe,eAAe,MAAM;AACvD,0BAAoB,IAAI,KAAK,UAAU,QAAW,EAAE,aAAa,WAAW,CAAC;AAAA,IAC9E;AAAA,EACD;AACA,SAAO;AACR;AAUO,UAAU,iBAAiB,KAAuC;AACxE,QAAM,YAAY,qBAAqB;AACvC,MAAI,WAAW;AACd,eAAW,EAAE,QAAQ,KAAK,UAAU,QAAQ,GAAG,EAAG,OAAM;AAAA,EACzD,OAAO;AACN,WAAO;AAAA,EACR;AACD;AAiBO,SAAS,kBAAkB,KAAqB;AACtD,MAAI,CAAC,IAAK,QAAO;AACjB,aAAW,WAAW,iBAAiB,GAAG,GAAG;AAC5C,WAAO;AAAA,EACR;AACA,SAAO;AACR;",
4
+ "sourcesContent": ["let graphemeSegmenter: Intl.Segmenter | undefined\n\n/**\n * Get the first character of a string, treating a multi-code-unit character such as an emoji as a\n * single character. Unlike `str[0]` or `str.charAt(0)`, which return one UTF-16 code unit and so\n * split an emoji into a broken half, this returns the first whole grapheme cluster. Returns an\n * empty string when the input is empty.\n *\n * @example\n * ```ts\n * getFirstCharacter('hello') // 'h'\n * getFirstCharacter('\uD83D\uDE00 hello') // '\uD83D\uDE00'\n * getFirstCharacter('') // ''\n * ```\n *\n * @public\n */\nexport function getFirstCharacter(str: string): string {\n\tif (!str) return ''\n\tgraphemeSegmenter ??= new Intl.Segmenter(undefined, { granularity: 'grapheme' })\n\tfor (const { segment } of graphemeSegmenter.segment(str)) {\n\t\treturn segment\n\t}\n\treturn ''\n}\n"],
5
+ "mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAAI;AAiBG,SAAS,kBAAkB,KAAqB;AACtD,MAAI,CAAC,IAAK,QAAO;AACjB,wBAAsB,IAAI,KAAK,UAAU,QAAW,EAAE,aAAa,WAAW,CAAC;AAC/E,aAAW,EAAE,QAAQ,KAAK,kBAAkB,QAAQ,GAAG,GAAG;AACzD,WAAO;AAAA,EACR;AACA,SAAO;AACR;",
6
6
  "names": []
7
7
  }
@@ -795,16 +795,6 @@ export declare function isNonNull<T>(value: T): value is typeof value extends nu
795
795
  */
796
796
  export declare function isNonNullish<T>(value: T): value is typeof value extends undefined ? never : typeof value extends null ? never : T;
797
797
 
798
- /**
799
- * Iterate over the grapheme clusters of a string — user-perceived characters such as emoji
800
- * sequences, flags, and accented letters — instead of UTF-16 code units. Uses `Intl.Segmenter`
801
- * where available and falls back to iterating by code point on browsers that lack it, where a
802
- * multi-code-point cluster is yielded as its component code points rather than as one segment.
803
- *
804
- * @public
805
- */
806
- export declare function iterateGraphemes(str: string): IterableIterator<string>;
807
-
808
798
  /**
809
799
  * A type representing a JSON array containing any valid JSON values.
810
800
  * Arrays can contain mixed types of JSON values including nested arrays and objects.
@@ -86,7 +86,7 @@ import {
86
86
  setInLocalStorage,
87
87
  setInSessionStorage
88
88
  } from "./lib/storage.mjs";
89
- import { getFirstCharacter, iterateGraphemes } from "./lib/string.mjs";
89
+ import { getFirstCharacter } from "./lib/string.mjs";
90
90
  import { stringEnum } from "./lib/stringEnum.mjs";
91
91
  import { FpsScheduler, fpsThrottle, throttleToNextFrame } from "./lib/throttle.mjs";
92
92
  import { Timers } from "./lib/timers.mjs";
@@ -103,7 +103,7 @@ import { registerTldrawLibraryVersion as registerTldrawLibraryVersion2 } from ".
103
103
  import { warnDeprecatedGetter, warnOnce } from "./lib/warn.mjs";
104
104
  registerTldrawLibraryVersion(
105
105
  "@tldraw/utils",
106
- "5.4.0-canary.fa89ff126c46",
106
+ "5.4.0-next.038cf4b0c054",
107
107
  "esm"
108
108
  );
109
109
  export {
@@ -168,7 +168,6 @@ export {
168
168
  isNativeStructuredClone,
169
169
  isNonNull,
170
170
  isNonNullish,
171
- iterateGraphemes,
172
171
  last,
173
172
  lerp,
174
173
  lns,
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/index.ts"],
4
- "sourcesContent": ["import { registerTldrawLibraryVersion } from './lib/version'\n\nexport { default as isEqual } from 'lodash.isequal'\nexport { default as isEqualWith } from 'lodash.isequalwith'\nexport { default as throttle } from 'lodash.throttle'\nexport { default as uniq } from 'lodash.uniq'\nexport {\n\tareArraysShallowEqual,\n\tcompact,\n\tdedupe,\n\tlast,\n\tmaxBy,\n\tmergeArraysAndReplaceDefaults,\n\tminBy,\n\tpartition,\n\trotateArray,\n} from './lib/array'\nexport { bind } from './lib/bind'\nexport { WeakCache } from './lib/cache'\nexport {\n\tassert,\n\tassertExists,\n\texhaustiveSwitchError,\n\tpromiseWithResolve,\n\tResult,\n\tsleep,\n\ttype ErrorResult,\n\ttype OkResult,\n} from './lib/control'\nexport { debounce } from './lib/debounce'\nexport { annotateError, getErrorAnnotations, type ErrorAnnotations } from './lib/error'\nexport { ExecutionQueue } from './lib/ExecutionQueue'\nexport { FileHelpers } from './lib/file'\nexport { noop, omitFromStackTrace } from './lib/function'\nexport { getHashForBuffer, getHashForObject, getHashForString, lns } from './lib/hash'\nexport { mockUniqueId, restoreUniqueId, uniqueId } from './lib/id'\nexport { getFirstFromIterable } from './lib/iterable'\nexport { LruCache } from './lib/LruCache'\nexport type { JsonArray, JsonObject, JsonPrimitive, JsonValue } from './lib/json-value'\nexport {\n\tDEFAULT_SUPPORT_VIDEO_TYPES,\n\tDEFAULT_SUPPORTED_IMAGE_TYPES,\n\tDEFAULT_SUPPORTED_MEDIA_TYPE_LIST,\n\tDEFAULT_SUPPORTED_MEDIA_TYPES,\n\tMediaHelpers,\n} from './lib/media/media'\nexport { PngHelpers } from './lib/media/png'\nexport { fetch, Image } from './lib/network'\nexport { invLerp, lerp, modulate, rng } from './lib/number'\nexport {\n\tareObjectsShallowEqual,\n\tfilterEntries,\n\tgetChangedKeys,\n\tgetOwnProperty,\n\tgroupBy,\n\thasOwnProperty,\n\tisEqualAllowingForFloatingPointErrors,\n\tmapObjectMapValues,\n\tobjectMapEntries,\n\tobjectMapEntriesIterable,\n\tobjectMapFromEntries,\n\tobjectMapKeys,\n\tobjectMapValues,\n\tomit,\n} from './lib/object'\nexport { measureAverageDuration, measureCbDuration, measureDuration } from './lib/perf'\nexport { PerformanceTracker } from './lib/PerformanceTracker'\nexport {\n\tgetIndexAbove,\n\tgetIndexBelow,\n\tgetIndexBetween,\n\tgetIndices,\n\tgetIndicesAbove,\n\tgetIndicesBelow,\n\tgetIndicesBetween,\n\tsortByIndex,\n\tsortByMaybeIndex,\n\tvalidateIndexKey,\n\tZERO_INDEX_KEY,\n\ttype IndexKey,\n} from './lib/reordering'\nexport { retry } from './lib/retry'\nexport { sortById } from './lib/sort'\nexport {\n\tclearLocalStorage,\n\tclearSessionStorage,\n\tdeleteFromLocalStorage,\n\tdeleteFromSessionStorage,\n\tgetFromLocalStorage,\n\tgetFromSessionStorage,\n\tsetInLocalStorage,\n\tsetInSessionStorage,\n} from './lib/storage'\nexport { getFirstCharacter, iterateGraphemes } from './lib/string'\nexport { stringEnum } from './lib/stringEnum'\nexport { FpsScheduler, fpsThrottle, throttleToNextFrame } from './lib/throttle'\nexport { Timers } from './lib/timers'\nexport {\n\ttype Awaitable,\n\ttype Expand,\n\ttype MakeUndefinedOptional,\n\ttype RecursivePartial,\n\ttype Required,\n} from './lib/types'\nexport { safeParseUrl } from './lib/url'\nexport {\n\tisDefined,\n\tisNativeStructuredClone,\n\tisNonNull,\n\tisNonNullish,\n\tSTRUCTURED_CLONE_OBJECT_PROTOTYPE,\n\tstructuredClone,\n} from './lib/value'\nexport { registerTldrawLibraryVersion } from './lib/version'\nexport { warnDeprecatedGetter, warnOnce } from './lib/warn'\n\nregisterTldrawLibraryVersion(\n\t(globalThis as any).TLDRAW_LIBRARY_NAME,\n\t(globalThis as any).TLDRAW_LIBRARY_VERSION,\n\t(globalThis as any).TLDRAW_LIBRARY_MODULES\n)\n"],
5
- "mappings": "AAAA,SAAS,oCAAoC;AAE7C,SAAoB,WAAXA,gBAA0B;AACnC,SAAoB,WAAXA,gBAA8B;AACvC,SAAoB,WAAXA,gBAA2B;AACpC,SAAoB,WAAXA,gBAAuB;AAChC;AAAA,EACC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACM;AACP,SAAS,YAAY;AACrB,SAAS,iBAAiB;AAC1B;AAAA,EACC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAGM;AACP,SAAS,gBAAgB;AACzB,SAAS,eAAe,2BAAkD;AAC1E,SAAS,sBAAsB;AAC/B,SAAS,mBAAmB;AAC5B,SAAS,MAAM,0BAA0B;AACzC,SAAS,kBAAkB,kBAAkB,kBAAkB,WAAW;AAC1E,SAAS,cAAc,iBAAiB,gBAAgB;AACxD,SAAS,4BAA4B;AACrC,SAAS,gBAAgB;AAEzB;AAAA,EACC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACM;AACP,SAAS,kBAAkB;AAC3B,SAAS,OAAO,aAAa;AAC7B,SAAS,SAAS,MAAM,UAAU,WAAW;AAC7C;AAAA,EACC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACM;AACP,SAAS,wBAAwB,mBAAmB,uBAAuB;AAC3E,SAAS,0BAA0B;AACnC;AAAA,EACC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAEM;AACP,SAAS,aAAa;AACtB,SAAS,gBAAgB;AACzB;AAAA,EACC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACM;AACP,SAAS,mBAAmB,wBAAwB;AACpD,SAAS,kBAAkB;AAC3B,SAAS,cAAc,aAAa,2BAA2B;AAC/D,SAAS,cAAc;AAQvB,SAAS,oBAAoB;AAC7B;AAAA,EACC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACM;AACP,SAAS,gCAAAC,qCAAoC;AAC7C,SAAS,sBAAsB,gBAAgB;AAE/C;AAAA,EACE;AAAA,EACA;AAAA,EACA;AACF;",
4
+ "sourcesContent": ["import { registerTldrawLibraryVersion } from './lib/version'\n\nexport { default as isEqual } from 'lodash.isequal'\nexport { default as isEqualWith } from 'lodash.isequalwith'\nexport { default as throttle } from 'lodash.throttle'\nexport { default as uniq } from 'lodash.uniq'\nexport {\n\tareArraysShallowEqual,\n\tcompact,\n\tdedupe,\n\tlast,\n\tmaxBy,\n\tmergeArraysAndReplaceDefaults,\n\tminBy,\n\tpartition,\n\trotateArray,\n} from './lib/array'\nexport { bind } from './lib/bind'\nexport { WeakCache } from './lib/cache'\nexport {\n\tassert,\n\tassertExists,\n\texhaustiveSwitchError,\n\tpromiseWithResolve,\n\tResult,\n\tsleep,\n\ttype ErrorResult,\n\ttype OkResult,\n} from './lib/control'\nexport { debounce } from './lib/debounce'\nexport { annotateError, getErrorAnnotations, type ErrorAnnotations } from './lib/error'\nexport { ExecutionQueue } from './lib/ExecutionQueue'\nexport { FileHelpers } from './lib/file'\nexport { noop, omitFromStackTrace } from './lib/function'\nexport { getHashForBuffer, getHashForObject, getHashForString, lns } from './lib/hash'\nexport { mockUniqueId, restoreUniqueId, uniqueId } from './lib/id'\nexport { getFirstFromIterable } from './lib/iterable'\nexport { LruCache } from './lib/LruCache'\nexport type { JsonArray, JsonObject, JsonPrimitive, JsonValue } from './lib/json-value'\nexport {\n\tDEFAULT_SUPPORT_VIDEO_TYPES,\n\tDEFAULT_SUPPORTED_IMAGE_TYPES,\n\tDEFAULT_SUPPORTED_MEDIA_TYPE_LIST,\n\tDEFAULT_SUPPORTED_MEDIA_TYPES,\n\tMediaHelpers,\n} from './lib/media/media'\nexport { PngHelpers } from './lib/media/png'\nexport { fetch, Image } from './lib/network'\nexport { invLerp, lerp, modulate, rng } from './lib/number'\nexport {\n\tareObjectsShallowEqual,\n\tfilterEntries,\n\tgetChangedKeys,\n\tgetOwnProperty,\n\tgroupBy,\n\thasOwnProperty,\n\tisEqualAllowingForFloatingPointErrors,\n\tmapObjectMapValues,\n\tobjectMapEntries,\n\tobjectMapEntriesIterable,\n\tobjectMapFromEntries,\n\tobjectMapKeys,\n\tobjectMapValues,\n\tomit,\n} from './lib/object'\nexport { measureAverageDuration, measureCbDuration, measureDuration } from './lib/perf'\nexport { PerformanceTracker } from './lib/PerformanceTracker'\nexport {\n\tgetIndexAbove,\n\tgetIndexBelow,\n\tgetIndexBetween,\n\tgetIndices,\n\tgetIndicesAbove,\n\tgetIndicesBelow,\n\tgetIndicesBetween,\n\tsortByIndex,\n\tsortByMaybeIndex,\n\tvalidateIndexKey,\n\tZERO_INDEX_KEY,\n\ttype IndexKey,\n} from './lib/reordering'\nexport { retry } from './lib/retry'\nexport { sortById } from './lib/sort'\nexport {\n\tclearLocalStorage,\n\tclearSessionStorage,\n\tdeleteFromLocalStorage,\n\tdeleteFromSessionStorage,\n\tgetFromLocalStorage,\n\tgetFromSessionStorage,\n\tsetInLocalStorage,\n\tsetInSessionStorage,\n} from './lib/storage'\nexport { getFirstCharacter } from './lib/string'\nexport { stringEnum } from './lib/stringEnum'\nexport { FpsScheduler, fpsThrottle, throttleToNextFrame } from './lib/throttle'\nexport { Timers } from './lib/timers'\nexport {\n\ttype Awaitable,\n\ttype Expand,\n\ttype MakeUndefinedOptional,\n\ttype RecursivePartial,\n\ttype Required,\n} from './lib/types'\nexport { safeParseUrl } from './lib/url'\nexport {\n\tisDefined,\n\tisNativeStructuredClone,\n\tisNonNull,\n\tisNonNullish,\n\tSTRUCTURED_CLONE_OBJECT_PROTOTYPE,\n\tstructuredClone,\n} from './lib/value'\nexport { registerTldrawLibraryVersion } from './lib/version'\nexport { warnDeprecatedGetter, warnOnce } from './lib/warn'\n\nregisterTldrawLibraryVersion(\n\t(globalThis as any).TLDRAW_LIBRARY_NAME,\n\t(globalThis as any).TLDRAW_LIBRARY_VERSION,\n\t(globalThis as any).TLDRAW_LIBRARY_MODULES\n)\n"],
5
+ "mappings": "AAAA,SAAS,oCAAoC;AAE7C,SAAoB,WAAXA,gBAA0B;AACnC,SAAoB,WAAXA,gBAA8B;AACvC,SAAoB,WAAXA,gBAA2B;AACpC,SAAoB,WAAXA,gBAAuB;AAChC;AAAA,EACC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACM;AACP,SAAS,YAAY;AACrB,SAAS,iBAAiB;AAC1B;AAAA,EACC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAGM;AACP,SAAS,gBAAgB;AACzB,SAAS,eAAe,2BAAkD;AAC1E,SAAS,sBAAsB;AAC/B,SAAS,mBAAmB;AAC5B,SAAS,MAAM,0BAA0B;AACzC,SAAS,kBAAkB,kBAAkB,kBAAkB,WAAW;AAC1E,SAAS,cAAc,iBAAiB,gBAAgB;AACxD,SAAS,4BAA4B;AACrC,SAAS,gBAAgB;AAEzB;AAAA,EACC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACM;AACP,SAAS,kBAAkB;AAC3B,SAAS,OAAO,aAAa;AAC7B,SAAS,SAAS,MAAM,UAAU,WAAW;AAC7C;AAAA,EACC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACM;AACP,SAAS,wBAAwB,mBAAmB,uBAAuB;AAC3E,SAAS,0BAA0B;AACnC;AAAA,EACC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAEM;AACP,SAAS,aAAa;AACtB,SAAS,gBAAgB;AACzB;AAAA,EACC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACM;AACP,SAAS,yBAAyB;AAClC,SAAS,kBAAkB;AAC3B,SAAS,cAAc,aAAa,2BAA2B;AAC/D,SAAS,cAAc;AAQvB,SAAS,oBAAoB;AAC7B;AAAA,EACC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACM;AACP,SAAS,gCAAAC,qCAAoC;AAC7C,SAAS,sBAAsB,gBAAgB;AAE/C;AAAA,EACE;AAAA,EACA;AAAA,EACA;AACF;",
6
6
  "names": ["default", "registerTldrawLibraryVersion"]
7
7
  }
@@ -1,31 +1,13 @@
1
1
  let graphemeSegmenter;
2
- let checkedForSegmenter = false;
3
- function getGraphemeSegmenter() {
4
- if (!checkedForSegmenter) {
5
- checkedForSegmenter = true;
6
- if (typeof Intl !== "undefined" && "Segmenter" in Intl) {
7
- graphemeSegmenter = new Intl.Segmenter(void 0, { granularity: "grapheme" });
8
- }
9
- }
10
- return graphemeSegmenter;
11
- }
12
- function* iterateGraphemes(str) {
13
- const segmenter = getGraphemeSegmenter();
14
- if (segmenter) {
15
- for (const { segment } of segmenter.segment(str)) yield segment;
16
- } else {
17
- yield* str;
18
- }
19
- }
20
2
  function getFirstCharacter(str) {
21
3
  if (!str) return "";
22
- for (const segment of iterateGraphemes(str)) {
4
+ graphemeSegmenter ??= new Intl.Segmenter(void 0, { granularity: "grapheme" });
5
+ for (const { segment } of graphemeSegmenter.segment(str)) {
23
6
  return segment;
24
7
  }
25
8
  return "";
26
9
  }
27
10
  export {
28
- getFirstCharacter,
29
- iterateGraphemes
11
+ getFirstCharacter
30
12
  };
31
13
  //# sourceMappingURL=string.mjs.map
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/lib/string.ts"],
4
- "sourcesContent": ["let graphemeSegmenter: Intl.Segmenter | undefined\nlet checkedForSegmenter = false\n\n/**\n * Resolve a shared grapheme `Intl.Segmenter`, or `undefined` when the runtime has no\n * `Intl.Segmenter`. `Intl.Segmenter` is unavailable on some older browsers (notably Firefox before\n * 125), so feature-detect it once rather than constructing it eagerly, which would throw at module\n * load and take down the whole app.\n */\nfunction getGraphemeSegmenter(): Intl.Segmenter | undefined {\n\tif (!checkedForSegmenter) {\n\t\tcheckedForSegmenter = true\n\t\tif (typeof Intl !== 'undefined' && 'Segmenter' in Intl) {\n\t\t\tgraphemeSegmenter = new Intl.Segmenter(undefined, { granularity: 'grapheme' })\n\t\t}\n\t}\n\treturn graphemeSegmenter\n}\n\n/**\n * Iterate over the grapheme clusters of a string \u2014 user-perceived characters such as emoji\n * sequences, flags, and accented letters \u2014 instead of UTF-16 code units. Uses `Intl.Segmenter`\n * where available and falls back to iterating by code point on browsers that lack it, where a\n * multi-code-point cluster is yielded as its component code points rather than as one segment.\n *\n * @public\n */\nexport function* iterateGraphemes(str: string): IterableIterator<string> {\n\tconst segmenter = getGraphemeSegmenter()\n\tif (segmenter) {\n\t\tfor (const { segment } of segmenter.segment(str)) yield segment\n\t} else {\n\t\tyield* str\n\t}\n}\n\n/**\n * Get the first character of a string, treating a multi-code-unit character such as an emoji as a\n * single character. Unlike `str[0]` or `str.charAt(0)`, which return one UTF-16 code unit and so\n * split an emoji into a broken half, this returns the first whole grapheme cluster. Returns an\n * empty string when the input is empty.\n *\n * @example\n * ```ts\n * getFirstCharacter('hello') // 'h'\n * getFirstCharacter('\uD83D\uDE00 hello') // '\uD83D\uDE00'\n * getFirstCharacter('') // ''\n * ```\n *\n * @public\n */\nexport function getFirstCharacter(str: string): string {\n\tif (!str) return ''\n\tfor (const segment of iterateGraphemes(str)) {\n\t\treturn segment\n\t}\n\treturn ''\n}\n"],
5
- "mappings": "AAAA,IAAI;AACJ,IAAI,sBAAsB;AAQ1B,SAAS,uBAAmD;AAC3D,MAAI,CAAC,qBAAqB;AACzB,0BAAsB;AACtB,QAAI,OAAO,SAAS,eAAe,eAAe,MAAM;AACvD,0BAAoB,IAAI,KAAK,UAAU,QAAW,EAAE,aAAa,WAAW,CAAC;AAAA,IAC9E;AAAA,EACD;AACA,SAAO;AACR;AAUO,UAAU,iBAAiB,KAAuC;AACxE,QAAM,YAAY,qBAAqB;AACvC,MAAI,WAAW;AACd,eAAW,EAAE,QAAQ,KAAK,UAAU,QAAQ,GAAG,EAAG,OAAM;AAAA,EACzD,OAAO;AACN,WAAO;AAAA,EACR;AACD;AAiBO,SAAS,kBAAkB,KAAqB;AACtD,MAAI,CAAC,IAAK,QAAO;AACjB,aAAW,WAAW,iBAAiB,GAAG,GAAG;AAC5C,WAAO;AAAA,EACR;AACA,SAAO;AACR;",
4
+ "sourcesContent": ["let graphemeSegmenter: Intl.Segmenter | undefined\n\n/**\n * Get the first character of a string, treating a multi-code-unit character such as an emoji as a\n * single character. Unlike `str[0]` or `str.charAt(0)`, which return one UTF-16 code unit and so\n * split an emoji into a broken half, this returns the first whole grapheme cluster. Returns an\n * empty string when the input is empty.\n *\n * @example\n * ```ts\n * getFirstCharacter('hello') // 'h'\n * getFirstCharacter('\uD83D\uDE00 hello') // '\uD83D\uDE00'\n * getFirstCharacter('') // ''\n * ```\n *\n * @public\n */\nexport function getFirstCharacter(str: string): string {\n\tif (!str) return ''\n\tgraphemeSegmenter ??= new Intl.Segmenter(undefined, { granularity: 'grapheme' })\n\tfor (const { segment } of graphemeSegmenter.segment(str)) {\n\t\treturn segment\n\t}\n\treturn ''\n}\n"],
5
+ "mappings": "AAAA,IAAI;AAiBG,SAAS,kBAAkB,KAAqB;AACtD,MAAI,CAAC,IAAK,QAAO;AACjB,wBAAsB,IAAI,KAAK,UAAU,QAAW,EAAE,aAAa,WAAW,CAAC;AAC/E,aAAW,EAAE,QAAQ,KAAK,kBAAkB,QAAQ,GAAG,GAAG;AACzD,WAAO;AAAA,EACR;AACA,SAAO;AACR;",
6
6
  "names": []
7
7
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@tldraw/utils",
3
3
  "description": "tldraw infinite canvas SDK (private utilities).",
4
- "version": "5.4.0-canary.fa89ff126c46",
4
+ "version": "5.4.0-next.038cf4b0c054",
5
5
  "author": {
6
6
  "name": "tldraw Inc.",
7
7
  "email": "hello@tldraw.com"
package/src/index.ts CHANGED
@@ -91,7 +91,7 @@ export {
91
91
  setInLocalStorage,
92
92
  setInSessionStorage,
93
93
  } from './lib/storage'
94
- export { getFirstCharacter, iterateGraphemes } from './lib/string'
94
+ export { getFirstCharacter } from './lib/string'
95
95
  export { stringEnum } from './lib/stringEnum'
96
96
  export { FpsScheduler, fpsThrottle, throttleToNextFrame } from './lib/throttle'
97
97
  export { Timers } from './lib/timers'
@@ -1,5 +1,4 @@
1
- import { afterEach, describe, expect, it, vi } from 'vitest'
2
- import { getFirstCharacter, iterateGraphemes } from './string'
1
+ import { getFirstCharacter } from './string'
3
2
 
4
3
  describe('getFirstCharacter', () => {
5
4
  it('returns the first character of a plain string', () => {
@@ -22,59 +21,3 @@ describe('getFirstCharacter', () => {
22
21
  expect(getFirstCharacter(' hi')).toBe(' ')
23
22
  })
24
23
  })
25
-
26
- describe('iterateGraphemes', () => {
27
- it('yields each grapheme cluster in order', () => {
28
- expect([...iterateGraphemes('a😀b')]).toEqual(['a', '😀', 'b'])
29
- })
30
-
31
- it('keeps a multi-code-point cluster whole', () => {
32
- expect([...iterateGraphemes('👨‍👩‍👧!')]).toEqual(['👨‍👩‍👧', '!'])
33
- })
34
-
35
- it('yields nothing for an empty string', () => {
36
- expect([...iterateGraphemes('')]).toEqual([])
37
- })
38
- })
39
-
40
- // On browsers without `Intl.Segmenter` (e.g. Firefox before 125) the module must not throw and must
41
- // still iterate; it falls back to code-point iteration. The segmenter is probed lazily on first
42
- // use, so `Intl.Segmenter` has to be absent at call time — reset the module and remove the global
43
- // for the duration of each call.
44
- describe('grapheme iteration without Intl.Segmenter', () => {
45
- afterEach(() => {
46
- vi.resetModules()
47
- })
48
-
49
- async function withoutSegmenter<T>(fn: (mod: typeof import('./string')) => T): Promise<T> {
50
- vi.resetModules()
51
- // `Intl.Segmenter` is typed read-only; take a mutable view so we can remove and restore it.
52
- const mutableIntl = Intl as { Segmenter?: typeof Intl.Segmenter }
53
- const original = mutableIntl.Segmenter
54
- delete mutableIntl.Segmenter
55
- expect('Segmenter' in Intl).toBe(false)
56
- try {
57
- return fn(await import('./string'))
58
- } finally {
59
- mutableIntl.Segmenter = original
60
- }
61
- }
62
-
63
- it('iterateGraphemes falls back to code-point iteration', async () => {
64
- await withoutSegmenter(({ iterateGraphemes }) => {
65
- expect([...iterateGraphemes('ab😀')]).toEqual(['a', 'b', '😀'])
66
- })
67
- })
68
-
69
- it('getFirstCharacter keeps a surrogate pair whole', async () => {
70
- await withoutSegmenter(({ getFirstCharacter }) => {
71
- expect(getFirstCharacter('😀 hello')).toBe('😀')
72
- })
73
- })
74
-
75
- it('getFirstCharacter splits a ZWJ cluster to its first code point rather than throwing', async () => {
76
- await withoutSegmenter(({ getFirstCharacter }) => {
77
- expect(getFirstCharacter('👨‍👩‍👧 family')).toBe('👨')
78
- })
79
- })
80
- })
package/src/lib/string.ts CHANGED
@@ -1,38 +1,4 @@
1
1
  let graphemeSegmenter: Intl.Segmenter | undefined
2
- let checkedForSegmenter = false
3
-
4
- /**
5
- * Resolve a shared grapheme `Intl.Segmenter`, or `undefined` when the runtime has no
6
- * `Intl.Segmenter`. `Intl.Segmenter` is unavailable on some older browsers (notably Firefox before
7
- * 125), so feature-detect it once rather than constructing it eagerly, which would throw at module
8
- * load and take down the whole app.
9
- */
10
- function getGraphemeSegmenter(): Intl.Segmenter | undefined {
11
- if (!checkedForSegmenter) {
12
- checkedForSegmenter = true
13
- if (typeof Intl !== 'undefined' && 'Segmenter' in Intl) {
14
- graphemeSegmenter = new Intl.Segmenter(undefined, { granularity: 'grapheme' })
15
- }
16
- }
17
- return graphemeSegmenter
18
- }
19
-
20
- /**
21
- * Iterate over the grapheme clusters of a string — user-perceived characters such as emoji
22
- * sequences, flags, and accented letters — instead of UTF-16 code units. Uses `Intl.Segmenter`
23
- * where available and falls back to iterating by code point on browsers that lack it, where a
24
- * multi-code-point cluster is yielded as its component code points rather than as one segment.
25
- *
26
- * @public
27
- */
28
- export function* iterateGraphemes(str: string): IterableIterator<string> {
29
- const segmenter = getGraphemeSegmenter()
30
- if (segmenter) {
31
- for (const { segment } of segmenter.segment(str)) yield segment
32
- } else {
33
- yield* str
34
- }
35
- }
36
2
 
37
3
  /**
38
4
  * Get the first character of a string, treating a multi-code-unit character such as an emoji as a
@@ -51,7 +17,8 @@ export function* iterateGraphemes(str: string): IterableIterator<string> {
51
17
  */
52
18
  export function getFirstCharacter(str: string): string {
53
19
  if (!str) return ''
54
- for (const segment of iterateGraphemes(str)) {
20
+ graphemeSegmenter ??= new Intl.Segmenter(undefined, { granularity: 'grapheme' })
21
+ for (const { segment } of graphemeSegmenter.segment(str)) {
55
22
  return segment
56
23
  }
57
24
  return ''