Some checks failed
Deploy analytics worker / Deploy analytics worker to ${{ (github.ref == 'refs/heads/production' && 'production') || (github.ref == 'refs/heads/main' && 'staging') || 'preview' }} (push) Failing after 1s
i18n upload strings / Upload latest strings (push) Failing after 1s
Deploy bemo / Deploy bemo to ${{ (github.ref == 'refs/heads/bemo-production' && 'production') || (github.ref == 'refs/heads/main' && 'staging') || 'preview' }} (push) Failing after 1s
Deploy grafana resources / ${{ github.event_name == 'push' && 'Deploy grafana resources' || 'Validate grafana resources (dry run)' }} (push) Failing after 0s
End to end tests (dotcom) / Check for relevant changes (push) Failing after 0s
End to end tests (examples) / Check for relevant changes (push) Failing after 0s
End to end tests (mermaid readability) / Check for relevant changes (push) Failing after 1s
End to end tests (perf) / Check for relevant changes (push) Failing after 0s
Publish editor extensions / Publish editor extensions (push) Failing after 1s
Publish SDK packages / Remove the publish-packages label (push) Has been skipped
Checks / Tests & checks (push) Has been cancelled
Checks / Build all projects (push) Has been cancelled
Deploy templates / Deploy templates (push) Has been cancelled
End to end tests (dotcom) / End to end tests (dotcom) (push) Has been cancelled
End to end tests (mermaid readability) / End to end tests (mermaid readability) (push) Has been cancelled
End to end tests (perf) / End to end tests (perf) (push) Has been cancelled
Deploy .com / Deploy dotcom to ${{ (github.ref == 'refs/heads/production' && 'production') || (github.ref == 'refs/heads/main' && 'staging') || 'preview' }} (push) Has been cancelled
End to end tests (examples) / End to end tests (examples) (push) Has been cancelled
Publish SDK packages / Publish SDK packages (push) Has been cancelled
Publish SDK packages / Tell user released version (push) Has been cancelled
Publish SDK packages / Publishes code templates to separate repositories (push) Has been cancelled
Publish SDK packages / Deploys the template demos with the new SDK version (push) Has been cancelled
59 lines
2.0 KiB
TypeScript
59 lines
2.0 KiB
TypeScript
let graphemeSegmenter: Intl.Segmenter | undefined
|
|
let checkedForSegmenter = false
|
|
|
|
/**
|
|
* Resolve a shared grapheme `Intl.Segmenter`, or `undefined` when the runtime has no
|
|
* `Intl.Segmenter`. `Intl.Segmenter` is unavailable on some older browsers (notably Firefox before
|
|
* 125), so feature-detect it once rather than constructing it eagerly, which would throw at module
|
|
* load and take down the whole app.
|
|
*/
|
|
function getGraphemeSegmenter(): Intl.Segmenter | undefined {
|
|
if (!checkedForSegmenter) {
|
|
checkedForSegmenter = true
|
|
if (typeof Intl !== 'undefined' && 'Segmenter' in Intl) {
|
|
graphemeSegmenter = new Intl.Segmenter(undefined, { granularity: 'grapheme' })
|
|
}
|
|
}
|
|
return graphemeSegmenter
|
|
}
|
|
|
|
/**
|
|
* Iterate over the grapheme clusters of a string — user-perceived characters such as emoji
|
|
* sequences, flags, and accented letters — instead of UTF-16 code units. Uses `Intl.Segmenter`
|
|
* where available and falls back to iterating by code point on browsers that lack it, where a
|
|
* multi-code-point cluster is yielded as its component code points rather than as one segment.
|
|
*
|
|
* @public
|
|
*/
|
|
export function* iterateGraphemes(str: string): IterableIterator<string> {
|
|
const segmenter = getGraphemeSegmenter()
|
|
if (segmenter) {
|
|
for (const { segment } of segmenter.segment(str)) yield segment
|
|
} else {
|
|
yield* str
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get the first character of a string, treating a multi-code-unit character such as an emoji as a
|
|
* single character. Unlike `str[0]` or `str.charAt(0)`, which return one UTF-16 code unit and so
|
|
* split an emoji into a broken half, this returns the first whole grapheme cluster. Returns an
|
|
* empty string when the input is empty.
|
|
*
|
|
* @example
|
|
* ```ts
|
|
* getFirstCharacter('hello') // 'h'
|
|
* getFirstCharacter('😀 hello') // '😀'
|
|
* getFirstCharacter('') // ''
|
|
* ```
|
|
*
|
|
* @public
|
|
*/
|
|
export function getFirstCharacter(str: string): string {
|
|
if (!str) return ''
|
|
for (const segment of iterateGraphemes(str)) {
|
|
return segment
|
|
}
|
|
return ''
|
|
}
|