@synstack/text 1.1.9 → 1.2.1
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/README.md +0 -3
- package/dist/text.index.cjs +5 -3
- package/dist/text.index.cjs.map +1 -1
- package/dist/text.index.d.cts +12 -2
- package/dist/text.index.d.ts +12 -2
- package/dist/text.index.js +5 -3
- package/dist/text.index.js.map +1 -1
- package/package.json +6 -6
- package/src/text.lib.ts +17 -5
package/README.md
CHANGED
@@ -4,9 +4,6 @@
|
|
4
4
|
|
5
5
|
This is a strongly opinionated implementation of string templating. It's basically JSX for text and solves many quirks of string interpolation and formatting.
|
6
6
|
|
7
|
-
> [!WARNING]
|
8
|
-
> This package is included in the [@synstack/synscript](https://github.com/pAIrprogioio/synscript) package. It is not recommended to install both packages at the same time.
|
9
|
-
|
10
7
|
## What is it for ?
|
11
8
|
|
12
9
|
Turns this:
|
package/dist/text.index.cjs
CHANGED
@@ -59,8 +59,10 @@ var Text = class _Text {
|
|
59
59
|
const value = values2[i];
|
60
60
|
let wrappedValue = "";
|
61
61
|
if (Array.isArray(value)) {
|
62
|
-
wrappedValue = value.filter(
|
63
|
-
|
62
|
+
wrappedValue = value.filter(
|
63
|
+
(inner) => inner !== null && inner !== void 0 && inner !== false
|
64
|
+
).map(_Text.wrapValue).join(this._options.joinString);
|
65
|
+
} else if (value !== null && value !== void 0 && value !== false) {
|
64
66
|
wrappedValue = _Text.wrapValue(value);
|
65
67
|
}
|
66
68
|
const nextString = template[i + 1];
|
@@ -72,7 +74,7 @@ var Text = class _Text {
|
|
72
74
|
}).$;
|
73
75
|
}
|
74
76
|
static wrapValue(value) {
|
75
|
-
if (value === null || value === void 0) return "";
|
77
|
+
if (value === null || value === void 0 || value === false) return "";
|
76
78
|
if (typeof value === "object") {
|
77
79
|
if (!Object.hasOwn(value, "type")) {
|
78
80
|
throw new Error(
|
package/dist/text.index.cjs.map
CHANGED
@@ -1 +1 @@
|
|
1
|
-
{"version":3,"sources":["../src/text.index.ts","../src/text.lib.ts"],"sourcesContent":["export * from \"./text.bundle.ts\";\nexport { TextParseExtraItemException } from \"./text.lib.ts\";\nexport type { Text } from \"./text.lib.ts\";\n","import { json } from \"@synstack/json\";\nimport { resolvable, type Resolvable } from \"@synstack/resolved\";\nimport { callable, type CallableResolvable } from \"@synstack/resolved/callable\";\nimport { str } from \"@synstack/str\";\nimport { type MaybeArray } from \"../../shared/src/ts.utils.ts\";\n\nexport class Text {\n private static EXTRA_OBJECT_PREFIX = \"%STR_EXTRA%\";\n private static EXTRA_OBJECT_SUFFIX = \"%!STR_EXTRA%\";\n\n private readonly _options: {\n joinString: string;\n };\n\n private constructor(options: Text.Options = {}) {\n this._options = {\n joinString: options.joinString ?? \"\\n\",\n };\n }\n\n public static options(this: void, config: Text.Options = {}) {\n return new Text(config);\n }\n\n public options(config: Text.Options) {\n return new Text({ ...this._options, ...config });\n }\n\n public static t<T extends Array<Text.TemplateValue.Base>>(\n this: void,\n template: TemplateStringsArray,\n ...values: T\n ): Text.Return<T> {\n return Text.options().t(template, ...values);\n }\n\n public t<T extends Array<Text.TemplateValue.Base>>(\n template: TemplateStringsArray,\n ...values: T\n ): Text.Return<T> {\n // @ts-expect-error - Of course the default value is \"\"\n if (template.length === 0) return \"\";\n\n const resolvedValues = callable.resolveNested(values);\n return resolvable.pipe(resolvedValues)._((values) => {\n let text = template[0];\n\n for (let i = 0; i < values.length; i++) {\n const value = values[i];\n let wrappedValue = \"\";\n\n if (Array.isArray(value)) {\n wrappedValue = value\n .filter((inner) => inner !== null || inner !== undefined)\n .map(Text.wrapValue)\n .join(this._options.joinString);\n } else {\n wrappedValue = Text.wrapValue(value);\n }\n const nextString = template[i + 1];\n const lastLine = str(text).lastLine();\n const indentation = lastLine.isEmpty()\n ? lastLine.leadingSpacesCount()\n : 0;\n text =\n str(text).chopEnd(indentation).toString() +\n str(wrappedValue).indent(indentation).toString() +\n nextString;\n }\n\n return str(text)\n .chopEmptyLinesStart()\n .trimEnd()\n .dedent()\n .trimEmptyLines()\n .chopRepeatNewlines(2)\n .toString() as string & {\n __extra: Text.ExtraObject.Infer<T>;\n };\n }).$ as Text.Return<T>;\n }\n\n private static wrapValue(this: void, value: Text.Value.Base) {\n if (value === null || value === undefined) return \"\";\n if (typeof value === \"object\") {\n if (!Object.hasOwn(value, \"type\")) {\n throw new Error(\n 'Text templating only supports objects with a \"type\" property',\n );\n }\n return `${Text.EXTRA_OBJECT_PREFIX}${JSON.stringify(value)}${\n Text.EXTRA_OBJECT_SUFFIX\n }`;\n }\n return value;\n }\n\n public static parse<E extends Text.ExtraObject.Base>(\n this: void,\n text: Text.String<E>,\n ): Array<string | E> {\n const regex = new RegExp(\n Text.EXTRA_OBJECT_PREFIX + \"(.*?)\" + Text.EXTRA_OBJECT_SUFFIX,\n \"g\",\n );\n const parts: Array<string | E> = [];\n let lastIndex = 0;\n let match;\n\n while ((match = regex.exec(text)) !== null) {\n // Add the text before the match\n if (match.index > lastIndex) {\n parts.push(text.slice(lastIndex, match.index));\n }\n\n // Parse and add the JSON object\n try {\n const jsonObject = json.deserialize<E>(match[1]);\n parts.push(jsonObject);\n } catch (error) {\n throw new TextParseExtraItemException(match[1], error);\n }\n\n lastIndex = regex.lastIndex;\n }\n\n // Add any remaining text after the last match\n if (lastIndex < text.length) {\n parts.push(text.slice(lastIndex));\n }\n\n return parts;\n }\n}\n\nexport declare namespace Text {\n export type Options = {\n joinString?: string;\n };\n\n export type OptionalString = string | undefined | null;\n\n export type String<TExtraObject extends Text.ExtraObject.Base = never> =\n string & {\n __extra: TExtraObject;\n };\n\n export namespace ExtraObject {\n export type Base = { type: string };\n\n type InferExtraObjectValue<T> = T extends Text.OptionalString ? never : T;\n type InferExtraObjectCallableResolvable<T> = InferExtraObjectValue<\n CallableResolvable.Infer<T>\n >;\n type InferExtraArrayable<T> =\n T extends Array<any>\n ? { [K in keyof T]: InferExtraObjectCallableResolvable<T[K]> }[number]\n : InferExtraObjectCallableResolvable<T>;\n\n export type Infer<T extends any[]> = {\n // Check if it's an array\n [K in keyof T]: InferExtraArrayable<T[K]>;\n }[number];\n }\n\n export type Value<TExtraObject extends Text.ExtraObject.Base = never> =\n | Text.OptionalString\n | TExtraObject;\n\n export namespace Value {\n export type Base = OptionalString | ExtraObject.Base;\n }\n\n export type TemplateValue<\n TExtraObject extends Text.ExtraObject.Base = never,\n > = CallableResolvable.MaybeArray<Value<TExtraObject>>;\n\n export namespace TemplateValue {\n export type Base = TemplateValue<ExtraObject.Base>;\n\n export type Resolved<TExtraObject extends Text.ExtraObject.Base = never> =\n MaybeArray<Value<TExtraObject>>;\n export namespace Resolved {\n export type Base = Resolved<ExtraObject.Base>;\n }\n }\n\n export type Return<T extends Array<Text.TemplateValue.Base>> =\n true extends CallableResolvable.MaybeArray.ArrayOf.IsPromise<T>\n ? Promise<string & { __extra: ExtraObject.Infer<T> }>\n : string & { __extra: ExtraObject.Infer<T> };\n}\n\nexport class TextParseExtraItemException extends Error {\n constructor(itemString: string, cause: any) {\n super(\n `\nFailed to parse extra item serialized value\n\nValue:\n${str(itemString).indent(2).toString()}\n\nCause:\n${str(cause instanceof Error ? cause.message : (cause as string))\n .indent(2)\n .toString()}\n\n`.trimStart(),\n { cause },\n );\n }\n}\n\n/**\n * @param condition - The condition to check, can be:\n * - primitive values\n * - promise of a primitive value\n * @returns The result of the template string if the resolved value is true, otherwise an empty string\n *\n * @example\n * ```ts\n * const text = tIf(true)`Hello World`;\n * assert.equal(text, \"Hello World\");\n * ```\n */\nexport const tIf =\n <CHECK extends Resolvable<any>>(condition: CHECK) =>\n <TValues extends Array<Text.TemplateValue.Base>>(\n template: TemplateStringsArray,\n ...args: TValues\n ): CHECK extends Promise<any>\n ? // CHECK is a promise\n Text.Return<TValues> extends Promise<any>\n ? // Text.Return<TValues> is a promise as well, so we don't need to wrap it in a promise\n Text.Return<TValues>\n : // Text.Return<TValues> is not a promise, so we need to wrap it in a promise\n Promise<Text.Return<TValues>>\n : // Neither are promises, so we don't need to wrap the return value in a promise\n Text.Return<TValues> => {\n if (!condition) return \"\" as any;\n\n if (condition instanceof Promise)\n return condition.then((c) =>\n c ? t(template, ...args) : (\"\" as any),\n ) as any;\n\n return t(template, ...args) as any;\n };\n\n/**\n * Serializes and formats a template string with values into a single string while preserving extra values typings\n * @returns The serialized and formatted string\n *\n * @features\n * - Promises even nested in arrays are resolved in parallel\n * - Array values are joined with a newline\n * - Text is trimmed\n * - Base indentation is removed\n * - Nested indentation is preserved for multi-line values\n * - Returned value is either a string or a promise of a string based on interpolated values\n * - Extra values are serialized and added to the returned value type\n *\n * @example\n * For the following template:\n * ```ts\n * const text: string = t`\n * Hello\n * ${\"- Item 1\\n- Item 2\"}\n * World\n * `;\n * ```\n *\n * The output will be:\n * ```plain\n * Hello\n * - Item 1\n * - Item 2\n * World\n * ```\n */\nexport const t = Text.t;\n\n/**\n * Deserializes a serialized string with extra values\n *\n * @param text - The text string to parse\n * @returns Array of strings and extra objects\n *\n * @example\n * ```ts\n * const text = t`Hello ${{type: \"item\", value: \"World\"}}`;\n * assert.equal(tParse(text), [\"Hello\", { type: \"item\", value: \"World\" }]);\n * ```\n */\nexport const tParse = Text.parse;\n"],"mappings":"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","names":["values"]}
|
1
|
+
{"version":3,"sources":["../src/text.index.ts","../src/text.lib.ts"],"sourcesContent":["export * from \"./text.bundle.ts\";\nexport { TextParseExtraItemException } from \"./text.lib.ts\";\nexport type { Text } from \"./text.lib.ts\";\n","import { json } from \"@synstack/json\";\nimport { resolvable, type Resolvable } from \"@synstack/resolved\";\nimport { callable, type CallableResolvable } from \"@synstack/resolved/callable\";\nimport { str } from \"@synstack/str\";\nimport { type MaybeArray } from \"../../shared/src/ts.utils.ts\";\n\nexport class Text {\n private static EXTRA_OBJECT_PREFIX = \"%STR_EXTRA%\";\n private static EXTRA_OBJECT_SUFFIX = \"%!STR_EXTRA%\";\n\n private readonly _options: {\n joinString: string;\n };\n\n private constructor(options: Text.Options = {}) {\n this._options = {\n joinString: options.joinString ?? \"\\n\",\n };\n }\n\n public static options(this: void, config: Text.Options = {}) {\n return new Text(config);\n }\n\n public options(config: Text.Options) {\n return new Text({ ...this._options, ...config });\n }\n\n public static t<T extends Array<Text.TemplateValue.Base>>(\n this: void,\n template: TemplateStringsArray,\n ...values: T\n ): Text.Return<T> {\n return Text.options().t(template, ...values);\n }\n\n public t<T extends Array<Text.TemplateValue.Base>>(\n template: TemplateStringsArray,\n ...values: T\n ): Text.Return<T> {\n // @ts-expect-error - Of course the default value is \"\"\n if (template.length === 0) return \"\";\n\n const resolvedValues = callable.resolveNested(values);\n return resolvable.pipe(resolvedValues)._((values) => {\n let text = template[0];\n\n for (let i = 0; i < values.length; i++) {\n const value = values[i];\n let wrappedValue = \"\";\n\n if (Array.isArray(value)) {\n wrappedValue = value\n .filter(\n (inner) =>\n inner !== null && inner !== undefined && inner !== false,\n )\n .map(Text.wrapValue)\n .join(this._options.joinString);\n } else if (value !== null && value !== undefined && value !== false) {\n wrappedValue = Text.wrapValue(value);\n }\n const nextString = template[i + 1];\n const lastLine = str(text).lastLine();\n const indentation = lastLine.isEmpty()\n ? lastLine.leadingSpacesCount()\n : 0;\n text =\n str(text).chopEnd(indentation).toString() +\n str(wrappedValue).indent(indentation).toString() +\n nextString;\n }\n\n return str(text)\n .chopEmptyLinesStart()\n .trimEnd()\n .dedent()\n .trimEmptyLines()\n .chopRepeatNewlines(2)\n .toString() as string & {\n __extra: Text.ExtraObject.Infer<T>;\n };\n }).$ as Text.Return<T>;\n }\n\n private static wrapValue(this: void, value: Text.Value.Base): string {\n if (value === null || value === undefined || value === false) return \"\";\n if (typeof value === \"object\") {\n if (!Object.hasOwn(value, \"type\")) {\n throw new Error(\n 'Text templating only supports objects with a \"type\" property',\n );\n }\n return `${Text.EXTRA_OBJECT_PREFIX}${JSON.stringify(value)}${\n Text.EXTRA_OBJECT_SUFFIX\n }`;\n }\n return value;\n }\n\n public static parse<E extends Text.ExtraObject.Base>(\n this: void,\n text: Text.String<E>,\n ): Array<string | E> {\n const regex = new RegExp(\n Text.EXTRA_OBJECT_PREFIX + \"(.*?)\" + Text.EXTRA_OBJECT_SUFFIX,\n \"g\",\n );\n const parts: Array<string | E> = [];\n let lastIndex = 0;\n let match;\n\n while ((match = regex.exec(text)) !== null) {\n // Add the text before the match\n if (match.index > lastIndex) {\n parts.push(text.slice(lastIndex, match.index));\n }\n\n // Parse and add the JSON object\n try {\n const jsonObject = json.deserialize<E>(match[1]);\n parts.push(jsonObject);\n } catch (error) {\n throw new TextParseExtraItemException(match[1], error);\n }\n\n lastIndex = regex.lastIndex;\n }\n\n // Add any remaining text after the last match\n if (lastIndex < text.length) {\n parts.push(text.slice(lastIndex));\n }\n\n return parts;\n }\n}\n\nexport declare namespace Text {\n export type Options = {\n joinString?: string;\n };\n\n export type OptionalString = string | undefined | null | false;\n\n export type String<TExtraObject extends Text.ExtraObject.Base = never> =\n string & {\n __extra: TExtraObject;\n };\n\n export namespace ExtraObject {\n export type Base = { type: string };\n\n type InferExtraObjectValue<T> = T extends Text.OptionalString ? never : T;\n type InferExtraObjectCallableResolvable<T> = InferExtraObjectValue<\n CallableResolvable.Infer<T>\n >;\n type InferExtraArrayable<T> =\n T extends Array<any>\n ? { [K in keyof T]: InferExtraObjectCallableResolvable<T[K]> }[number]\n : InferExtraObjectCallableResolvable<T>;\n\n export type Infer<T extends any[]> = {\n // Check if it's an array\n [K in keyof T]: InferExtraArrayable<T[K]>;\n }[number];\n }\n\n export type Value<TExtraObject extends Text.ExtraObject.Base = never> =\n | Text.OptionalString\n | TExtraObject;\n\n export namespace Value {\n export type Base = OptionalString | ExtraObject.Base;\n }\n\n export type TemplateValue<\n TExtraObject extends Text.ExtraObject.Base = never,\n > = CallableResolvable.MaybeArray<Value<TExtraObject>>;\n\n export namespace TemplateValue {\n export type Base = TemplateValue<ExtraObject.Base>;\n\n export type Resolved<TExtraObject extends Text.ExtraObject.Base = never> =\n MaybeArray<Value<TExtraObject>>;\n export namespace Resolved {\n export type Base = Resolved<ExtraObject.Base>;\n }\n }\n\n export type Return<T extends Array<Text.TemplateValue.Base>> =\n true extends CallableResolvable.MaybeArray.ArrayOf.IsPromise<T>\n ? Promise<string & { __extra: ExtraObject.Infer<T> }>\n : string & { __extra: ExtraObject.Infer<T> };\n}\n\nexport class TextParseExtraItemException extends Error {\n constructor(itemString: string, cause: any) {\n super(\n `\nFailed to parse extra item serialized value\n\nValue:\n${str(itemString).indent(2).toString()}\n\nCause:\n${str(cause instanceof Error ? cause.message : (cause as string))\n .indent(2)\n .toString()}\n\n`.trimStart(),\n { cause },\n );\n }\n}\n\n/**\n * @deprecated\n * Use the `&&` operator instead. `t` will filter out null, undefined, and false values.\n * Conditions are prefered over `tIf` because with `tIf` the interpolated values are still executed even if the value is falsy and the condition is not met.\n * `tIf` also doesn't support type narrowing to the interpolated values so this will trigger a type error:\n * ```ts\n * tIf(typeof value === \"string\")`${value}` // Likely type error as the type of value is not narrowed to a string\n * ```\n *\n * @param condition - The condition to check, can be:\n * - primitive values\n * - promise of a primitive value\n *\n * @returns The result of the template string if the resolved value is true, otherwise an empty string\n *\n * @example\n * ```ts\n * const text = tIf(true)`Hello World`;\n * assert.equal(text, \"Hello World\");\n * ```\n */\nexport const tIf =\n <CHECK extends Resolvable<any>>(condition: CHECK) =>\n <TValues extends Array<Text.TemplateValue.Base>>(\n template: TemplateStringsArray,\n ...args: TValues\n ): CHECK extends Promise<any>\n ? // CHECK is a promise\n Text.Return<TValues> extends Promise<any>\n ? // Text.Return<TValues> is a promise as well, so we don't need to wrap it in a promise\n Text.Return<TValues>\n : // Text.Return<TValues> is not a promise, so we need to wrap it in a promise\n Promise<Text.Return<TValues>>\n : // Neither are promises, so we don't need to wrap the return value in a promise\n Text.Return<TValues> => {\n if (!condition) return \"\" as any;\n\n if (condition instanceof Promise)\n return condition.then((c) =>\n c ? t(template, ...args) : (\"\" as any),\n ) as any;\n\n return t(template, ...args) as any;\n };\n\n/**\n * Serializes and formats a template string with values into a single string while preserving extra values typings\n * @returns The serialized and formatted string\n *\n * @features\n * - Promises even nested in arrays are resolved in parallel\n * - Array values are joined with a newline\n * - Text is trimmed\n * - Base indentation is removed\n * - Nested indentation is preserved for multi-line values\n * - Returned value is either a string or a promise of a string based on interpolated values\n * - Extra values are serialized and added to the returned value type\n *\n * @example\n * For the following template:\n * ```ts\n * const text: string = t`\n * Hello\n * ${\"- Item 1\\n- Item 2\"}\n * World\n * `;\n * ```\n *\n * The output will be:\n * ```plain\n * Hello\n * - Item 1\n * - Item 2\n * World\n * ```\n */\nexport const t = Text.t;\n\n/**\n * Deserializes a serialized string with extra values\n *\n * @param text - The text string to parse\n * @returns Array of strings and extra objects\n *\n * @example\n * ```ts\n * const text = t`Hello ${{type: \"item\", value: \"World\"}}`;\n * assert.equal(tParse(text), [\"Hello\", { type: \"item\", value: \"World\" }]);\n * ```\n */\nexport const tParse = Text.parse;\n"],"mappings":"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","names":["values"]}
|
package/dist/text.index.d.cts
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
import { Resolvable } from '@synstack/resolved';
|
1
2
|
import { CallableResolvable } from '@synstack/resolved/callable';
|
2
3
|
|
3
4
|
/**
|
@@ -21,7 +22,7 @@ declare namespace Text {
|
|
21
22
|
type Options = {
|
22
23
|
joinString?: string;
|
23
24
|
};
|
24
|
-
type OptionalString = string | undefined | null;
|
25
|
+
type OptionalString = string | undefined | null | false;
|
25
26
|
type String<TExtraObject extends Text.ExtraObject.Base = never> = string & {
|
26
27
|
__extra: TExtraObject;
|
27
28
|
};
|
@@ -60,9 +61,18 @@ declare class TextParseExtraItemException extends Error {
|
|
60
61
|
constructor(itemString: string, cause: any);
|
61
62
|
}
|
62
63
|
/**
|
64
|
+
* @deprecated
|
65
|
+
* Use the `&&` operator instead. `t` will filter out null, undefined, and false values.
|
66
|
+
* Conditions are prefered over `tIf` because with `tIf` the interpolated values are still executed even if the value is falsy and the condition is not met.
|
67
|
+
* `tIf` also doesn't support type narrowing to the interpolated values so this will trigger a type error:
|
68
|
+
* ```ts
|
69
|
+
* tIf(typeof value === "string")`${value}` // Likely type error as the type of value is not narrowed to a string
|
70
|
+
* ```
|
71
|
+
*
|
63
72
|
* @param condition - The condition to check, can be:
|
64
73
|
* - primitive values
|
65
74
|
* - promise of a primitive value
|
75
|
+
*
|
66
76
|
* @returns The result of the template string if the resolved value is true, otherwise an empty string
|
67
77
|
*
|
68
78
|
* @example
|
@@ -71,7 +81,7 @@ declare class TextParseExtraItemException extends Error {
|
|
71
81
|
* assert.equal(text, "Hello World");
|
72
82
|
* ```
|
73
83
|
*/
|
74
|
-
declare const tIf: <CHECK extends
|
84
|
+
declare const tIf: <CHECK extends Resolvable<any>>(condition: CHECK) => <TValues extends Array<Text.TemplateValue.Base>>(template: TemplateStringsArray, ...args: TValues) => CHECK extends Promise<any> ? Text.Return<TValues> extends Promise<any> ? Text.Return<TValues> : Promise<Text.Return<TValues>> : Text.Return<TValues>;
|
75
85
|
/**
|
76
86
|
* Serializes and formats a template string with values into a single string while preserving extra values typings
|
77
87
|
* @returns The serialized and formatted string
|
package/dist/text.index.d.ts
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
import { Resolvable } from '@synstack/resolved';
|
1
2
|
import { CallableResolvable } from '@synstack/resolved/callable';
|
2
3
|
|
3
4
|
/**
|
@@ -21,7 +22,7 @@ declare namespace Text {
|
|
21
22
|
type Options = {
|
22
23
|
joinString?: string;
|
23
24
|
};
|
24
|
-
type OptionalString = string | undefined | null;
|
25
|
+
type OptionalString = string | undefined | null | false;
|
25
26
|
type String<TExtraObject extends Text.ExtraObject.Base = never> = string & {
|
26
27
|
__extra: TExtraObject;
|
27
28
|
};
|
@@ -60,9 +61,18 @@ declare class TextParseExtraItemException extends Error {
|
|
60
61
|
constructor(itemString: string, cause: any);
|
61
62
|
}
|
62
63
|
/**
|
64
|
+
* @deprecated
|
65
|
+
* Use the `&&` operator instead. `t` will filter out null, undefined, and false values.
|
66
|
+
* Conditions are prefered over `tIf` because with `tIf` the interpolated values are still executed even if the value is falsy and the condition is not met.
|
67
|
+
* `tIf` also doesn't support type narrowing to the interpolated values so this will trigger a type error:
|
68
|
+
* ```ts
|
69
|
+
* tIf(typeof value === "string")`${value}` // Likely type error as the type of value is not narrowed to a string
|
70
|
+
* ```
|
71
|
+
*
|
63
72
|
* @param condition - The condition to check, can be:
|
64
73
|
* - primitive values
|
65
74
|
* - promise of a primitive value
|
75
|
+
*
|
66
76
|
* @returns The result of the template string if the resolved value is true, otherwise an empty string
|
67
77
|
*
|
68
78
|
* @example
|
@@ -71,7 +81,7 @@ declare class TextParseExtraItemException extends Error {
|
|
71
81
|
* assert.equal(text, "Hello World");
|
72
82
|
* ```
|
73
83
|
*/
|
74
|
-
declare const tIf: <CHECK extends
|
84
|
+
declare const tIf: <CHECK extends Resolvable<any>>(condition: CHECK) => <TValues extends Array<Text.TemplateValue.Base>>(template: TemplateStringsArray, ...args: TValues) => CHECK extends Promise<any> ? Text.Return<TValues> extends Promise<any> ? Text.Return<TValues> : Promise<Text.Return<TValues>> : Text.Return<TValues>;
|
75
85
|
/**
|
76
86
|
* Serializes and formats a template string with values into a single string while preserving extra values typings
|
77
87
|
* @returns The serialized and formatted string
|
package/dist/text.index.js
CHANGED
@@ -30,8 +30,10 @@ var Text = class _Text {
|
|
30
30
|
const value = values2[i];
|
31
31
|
let wrappedValue = "";
|
32
32
|
if (Array.isArray(value)) {
|
33
|
-
wrappedValue = value.filter(
|
34
|
-
|
33
|
+
wrappedValue = value.filter(
|
34
|
+
(inner) => inner !== null && inner !== void 0 && inner !== false
|
35
|
+
).map(_Text.wrapValue).join(this._options.joinString);
|
36
|
+
} else if (value !== null && value !== void 0 && value !== false) {
|
35
37
|
wrappedValue = _Text.wrapValue(value);
|
36
38
|
}
|
37
39
|
const nextString = template[i + 1];
|
@@ -43,7 +45,7 @@ var Text = class _Text {
|
|
43
45
|
}).$;
|
44
46
|
}
|
45
47
|
static wrapValue(value) {
|
46
|
-
if (value === null || value === void 0) return "";
|
48
|
+
if (value === null || value === void 0 || value === false) return "";
|
47
49
|
if (typeof value === "object") {
|
48
50
|
if (!Object.hasOwn(value, "type")) {
|
49
51
|
throw new Error(
|
package/dist/text.index.js.map
CHANGED
@@ -1 +1 @@
|
|
1
|
-
{"version":3,"sources":["../src/text.lib.ts"],"sourcesContent":["import { json } from \"@synstack/json\";\nimport { resolvable, type Resolvable } from \"@synstack/resolved\";\nimport { callable, type CallableResolvable } from \"@synstack/resolved/callable\";\nimport { str } from \"@synstack/str\";\nimport { type MaybeArray } from \"../../shared/src/ts.utils.ts\";\n\nexport class Text {\n private static EXTRA_OBJECT_PREFIX = \"%STR_EXTRA%\";\n private static EXTRA_OBJECT_SUFFIX = \"%!STR_EXTRA%\";\n\n private readonly _options: {\n joinString: string;\n };\n\n private constructor(options: Text.Options = {}) {\n this._options = {\n joinString: options.joinString ?? \"\\n\",\n };\n }\n\n public static options(this: void, config: Text.Options = {}) {\n return new Text(config);\n }\n\n public options(config: Text.Options) {\n return new Text({ ...this._options, ...config });\n }\n\n public static t<T extends Array<Text.TemplateValue.Base>>(\n this: void,\n template: TemplateStringsArray,\n ...values: T\n ): Text.Return<T> {\n return Text.options().t(template, ...values);\n }\n\n public t<T extends Array<Text.TemplateValue.Base>>(\n template: TemplateStringsArray,\n ...values: T\n ): Text.Return<T> {\n // @ts-expect-error - Of course the default value is \"\"\n if (template.length === 0) return \"\";\n\n const resolvedValues = callable.resolveNested(values);\n return resolvable.pipe(resolvedValues)._((values) => {\n let text = template[0];\n\n for (let i = 0; i < values.length; i++) {\n const value = values[i];\n let wrappedValue = \"\";\n\n if (Array.isArray(value)) {\n wrappedValue = value\n .filter((inner) => inner !== null || inner !== undefined)\n .map(Text.wrapValue)\n .join(this._options.joinString);\n } else {\n wrappedValue = Text.wrapValue(value);\n }\n const nextString = template[i + 1];\n const lastLine = str(text).lastLine();\n const indentation = lastLine.isEmpty()\n ? lastLine.leadingSpacesCount()\n : 0;\n text =\n str(text).chopEnd(indentation).toString() +\n str(wrappedValue).indent(indentation).toString() +\n nextString;\n }\n\n return str(text)\n .chopEmptyLinesStart()\n .trimEnd()\n .dedent()\n .trimEmptyLines()\n .chopRepeatNewlines(2)\n .toString() as string & {\n __extra: Text.ExtraObject.Infer<T>;\n };\n }).$ as Text.Return<T>;\n }\n\n private static wrapValue(this: void, value: Text.Value.Base) {\n if (value === null || value === undefined) return \"\";\n if (typeof value === \"object\") {\n if (!Object.hasOwn(value, \"type\")) {\n throw new Error(\n 'Text templating only supports objects with a \"type\" property',\n );\n }\n return `${Text.EXTRA_OBJECT_PREFIX}${JSON.stringify(value)}${\n Text.EXTRA_OBJECT_SUFFIX\n }`;\n }\n return value;\n }\n\n public static parse<E extends Text.ExtraObject.Base>(\n this: void,\n text: Text.String<E>,\n ): Array<string | E> {\n const regex = new RegExp(\n Text.EXTRA_OBJECT_PREFIX + \"(.*?)\" + Text.EXTRA_OBJECT_SUFFIX,\n \"g\",\n );\n const parts: Array<string | E> = [];\n let lastIndex = 0;\n let match;\n\n while ((match = regex.exec(text)) !== null) {\n // Add the text before the match\n if (match.index > lastIndex) {\n parts.push(text.slice(lastIndex, match.index));\n }\n\n // Parse and add the JSON object\n try {\n const jsonObject = json.deserialize<E>(match[1]);\n parts.push(jsonObject);\n } catch (error) {\n throw new TextParseExtraItemException(match[1], error);\n }\n\n lastIndex = regex.lastIndex;\n }\n\n // Add any remaining text after the last match\n if (lastIndex < text.length) {\n parts.push(text.slice(lastIndex));\n }\n\n return parts;\n }\n}\n\nexport declare namespace Text {\n export type Options = {\n joinString?: string;\n };\n\n export type OptionalString = string | undefined | null;\n\n export type String<TExtraObject extends Text.ExtraObject.Base = never> =\n string & {\n __extra: TExtraObject;\n };\n\n export namespace ExtraObject {\n export type Base = { type: string };\n\n type InferExtraObjectValue<T> = T extends Text.OptionalString ? never : T;\n type InferExtraObjectCallableResolvable<T> = InferExtraObjectValue<\n CallableResolvable.Infer<T>\n >;\n type InferExtraArrayable<T> =\n T extends Array<any>\n ? { [K in keyof T]: InferExtraObjectCallableResolvable<T[K]> }[number]\n : InferExtraObjectCallableResolvable<T>;\n\n export type Infer<T extends any[]> = {\n // Check if it's an array\n [K in keyof T]: InferExtraArrayable<T[K]>;\n }[number];\n }\n\n export type Value<TExtraObject extends Text.ExtraObject.Base = never> =\n | Text.OptionalString\n | TExtraObject;\n\n export namespace Value {\n export type Base = OptionalString | ExtraObject.Base;\n }\n\n export type TemplateValue<\n TExtraObject extends Text.ExtraObject.Base = never,\n > = CallableResolvable.MaybeArray<Value<TExtraObject>>;\n\n export namespace TemplateValue {\n export type Base = TemplateValue<ExtraObject.Base>;\n\n export type Resolved<TExtraObject extends Text.ExtraObject.Base = never> =\n MaybeArray<Value<TExtraObject>>;\n export namespace Resolved {\n export type Base = Resolved<ExtraObject.Base>;\n }\n }\n\n export type Return<T extends Array<Text.TemplateValue.Base>> =\n true extends CallableResolvable.MaybeArray.ArrayOf.IsPromise<T>\n ? Promise<string & { __extra: ExtraObject.Infer<T> }>\n : string & { __extra: ExtraObject.Infer<T> };\n}\n\nexport class TextParseExtraItemException extends Error {\n constructor(itemString: string, cause: any) {\n super(\n `\nFailed to parse extra item serialized value\n\nValue:\n${str(itemString).indent(2).toString()}\n\nCause:\n${str(cause instanceof Error ? cause.message : (cause as string))\n .indent(2)\n .toString()}\n\n`.trimStart(),\n { cause },\n );\n }\n}\n\n/**\n * @param condition - The condition to check, can be:\n * - primitive values\n * - promise of a primitive value\n * @returns The result of the template string if the resolved value is true, otherwise an empty string\n *\n * @example\n * ```ts\n * const text = tIf(true)`Hello World`;\n * assert.equal(text, \"Hello World\");\n * ```\n */\nexport const tIf =\n <CHECK extends Resolvable<any>>(condition: CHECK) =>\n <TValues extends Array<Text.TemplateValue.Base>>(\n template: TemplateStringsArray,\n ...args: TValues\n ): CHECK extends Promise<any>\n ? // CHECK is a promise\n Text.Return<TValues> extends Promise<any>\n ? // Text.Return<TValues> is a promise as well, so we don't need to wrap it in a promise\n Text.Return<TValues>\n : // Text.Return<TValues> is not a promise, so we need to wrap it in a promise\n Promise<Text.Return<TValues>>\n : // Neither are promises, so we don't need to wrap the return value in a promise\n Text.Return<TValues> => {\n if (!condition) return \"\" as any;\n\n if (condition instanceof Promise)\n return condition.then((c) =>\n c ? t(template, ...args) : (\"\" as any),\n ) as any;\n\n return t(template, ...args) as any;\n };\n\n/**\n * Serializes and formats a template string with values into a single string while preserving extra values typings\n * @returns The serialized and formatted string\n *\n * @features\n * - Promises even nested in arrays are resolved in parallel\n * - Array values are joined with a newline\n * - Text is trimmed\n * - Base indentation is removed\n * - Nested indentation is preserved for multi-line values\n * - Returned value is either a string or a promise of a string based on interpolated values\n * - Extra values are serialized and added to the returned value type\n *\n * @example\n * For the following template:\n * ```ts\n * const text: string = t`\n * Hello\n * ${\"- Item 1\\n- Item 2\"}\n * World\n * `;\n * ```\n *\n * The output will be:\n * ```plain\n * Hello\n * - Item 1\n * - Item 2\n * World\n * ```\n */\nexport const t = Text.t;\n\n/**\n * Deserializes a serialized string with extra values\n *\n * @param text - The text string to parse\n * @returns Array of strings and extra objects\n *\n * @example\n * ```ts\n * const text = t`Hello ${{type: \"item\", value: \"World\"}}`;\n * assert.equal(tParse(text), [\"Hello\", { type: \"item\", value: \"World\" }]);\n * ```\n */\nexport const tParse = Text.parse;\n"],"mappings":"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","names":["values"]}
|
1
|
+
{"version":3,"sources":["../src/text.lib.ts"],"sourcesContent":["import { json } from \"@synstack/json\";\nimport { resolvable, type Resolvable } from \"@synstack/resolved\";\nimport { callable, type CallableResolvable } from \"@synstack/resolved/callable\";\nimport { str } from \"@synstack/str\";\nimport { type MaybeArray } from \"../../shared/src/ts.utils.ts\";\n\nexport class Text {\n private static EXTRA_OBJECT_PREFIX = \"%STR_EXTRA%\";\n private static EXTRA_OBJECT_SUFFIX = \"%!STR_EXTRA%\";\n\n private readonly _options: {\n joinString: string;\n };\n\n private constructor(options: Text.Options = {}) {\n this._options = {\n joinString: options.joinString ?? \"\\n\",\n };\n }\n\n public static options(this: void, config: Text.Options = {}) {\n return new Text(config);\n }\n\n public options(config: Text.Options) {\n return new Text({ ...this._options, ...config });\n }\n\n public static t<T extends Array<Text.TemplateValue.Base>>(\n this: void,\n template: TemplateStringsArray,\n ...values: T\n ): Text.Return<T> {\n return Text.options().t(template, ...values);\n }\n\n public t<T extends Array<Text.TemplateValue.Base>>(\n template: TemplateStringsArray,\n ...values: T\n ): Text.Return<T> {\n // @ts-expect-error - Of course the default value is \"\"\n if (template.length === 0) return \"\";\n\n const resolvedValues = callable.resolveNested(values);\n return resolvable.pipe(resolvedValues)._((values) => {\n let text = template[0];\n\n for (let i = 0; i < values.length; i++) {\n const value = values[i];\n let wrappedValue = \"\";\n\n if (Array.isArray(value)) {\n wrappedValue = value\n .filter(\n (inner) =>\n inner !== null && inner !== undefined && inner !== false,\n )\n .map(Text.wrapValue)\n .join(this._options.joinString);\n } else if (value !== null && value !== undefined && value !== false) {\n wrappedValue = Text.wrapValue(value);\n }\n const nextString = template[i + 1];\n const lastLine = str(text).lastLine();\n const indentation = lastLine.isEmpty()\n ? lastLine.leadingSpacesCount()\n : 0;\n text =\n str(text).chopEnd(indentation).toString() +\n str(wrappedValue).indent(indentation).toString() +\n nextString;\n }\n\n return str(text)\n .chopEmptyLinesStart()\n .trimEnd()\n .dedent()\n .trimEmptyLines()\n .chopRepeatNewlines(2)\n .toString() as string & {\n __extra: Text.ExtraObject.Infer<T>;\n };\n }).$ as Text.Return<T>;\n }\n\n private static wrapValue(this: void, value: Text.Value.Base): string {\n if (value === null || value === undefined || value === false) return \"\";\n if (typeof value === \"object\") {\n if (!Object.hasOwn(value, \"type\")) {\n throw new Error(\n 'Text templating only supports objects with a \"type\" property',\n );\n }\n return `${Text.EXTRA_OBJECT_PREFIX}${JSON.stringify(value)}${\n Text.EXTRA_OBJECT_SUFFIX\n }`;\n }\n return value;\n }\n\n public static parse<E extends Text.ExtraObject.Base>(\n this: void,\n text: Text.String<E>,\n ): Array<string | E> {\n const regex = new RegExp(\n Text.EXTRA_OBJECT_PREFIX + \"(.*?)\" + Text.EXTRA_OBJECT_SUFFIX,\n \"g\",\n );\n const parts: Array<string | E> = [];\n let lastIndex = 0;\n let match;\n\n while ((match = regex.exec(text)) !== null) {\n // Add the text before the match\n if (match.index > lastIndex) {\n parts.push(text.slice(lastIndex, match.index));\n }\n\n // Parse and add the JSON object\n try {\n const jsonObject = json.deserialize<E>(match[1]);\n parts.push(jsonObject);\n } catch (error) {\n throw new TextParseExtraItemException(match[1], error);\n }\n\n lastIndex = regex.lastIndex;\n }\n\n // Add any remaining text after the last match\n if (lastIndex < text.length) {\n parts.push(text.slice(lastIndex));\n }\n\n return parts;\n }\n}\n\nexport declare namespace Text {\n export type Options = {\n joinString?: string;\n };\n\n export type OptionalString = string | undefined | null | false;\n\n export type String<TExtraObject extends Text.ExtraObject.Base = never> =\n string & {\n __extra: TExtraObject;\n };\n\n export namespace ExtraObject {\n export type Base = { type: string };\n\n type InferExtraObjectValue<T> = T extends Text.OptionalString ? never : T;\n type InferExtraObjectCallableResolvable<T> = InferExtraObjectValue<\n CallableResolvable.Infer<T>\n >;\n type InferExtraArrayable<T> =\n T extends Array<any>\n ? { [K in keyof T]: InferExtraObjectCallableResolvable<T[K]> }[number]\n : InferExtraObjectCallableResolvable<T>;\n\n export type Infer<T extends any[]> = {\n // Check if it's an array\n [K in keyof T]: InferExtraArrayable<T[K]>;\n }[number];\n }\n\n export type Value<TExtraObject extends Text.ExtraObject.Base = never> =\n | Text.OptionalString\n | TExtraObject;\n\n export namespace Value {\n export type Base = OptionalString | ExtraObject.Base;\n }\n\n export type TemplateValue<\n TExtraObject extends Text.ExtraObject.Base = never,\n > = CallableResolvable.MaybeArray<Value<TExtraObject>>;\n\n export namespace TemplateValue {\n export type Base = TemplateValue<ExtraObject.Base>;\n\n export type Resolved<TExtraObject extends Text.ExtraObject.Base = never> =\n MaybeArray<Value<TExtraObject>>;\n export namespace Resolved {\n export type Base = Resolved<ExtraObject.Base>;\n }\n }\n\n export type Return<T extends Array<Text.TemplateValue.Base>> =\n true extends CallableResolvable.MaybeArray.ArrayOf.IsPromise<T>\n ? Promise<string & { __extra: ExtraObject.Infer<T> }>\n : string & { __extra: ExtraObject.Infer<T> };\n}\n\nexport class TextParseExtraItemException extends Error {\n constructor(itemString: string, cause: any) {\n super(\n `\nFailed to parse extra item serialized value\n\nValue:\n${str(itemString).indent(2).toString()}\n\nCause:\n${str(cause instanceof Error ? cause.message : (cause as string))\n .indent(2)\n .toString()}\n\n`.trimStart(),\n { cause },\n );\n }\n}\n\n/**\n * @deprecated\n * Use the `&&` operator instead. `t` will filter out null, undefined, and false values.\n * Conditions are prefered over `tIf` because with `tIf` the interpolated values are still executed even if the value is falsy and the condition is not met.\n * `tIf` also doesn't support type narrowing to the interpolated values so this will trigger a type error:\n * ```ts\n * tIf(typeof value === \"string\")`${value}` // Likely type error as the type of value is not narrowed to a string\n * ```\n *\n * @param condition - The condition to check, can be:\n * - primitive values\n * - promise of a primitive value\n *\n * @returns The result of the template string if the resolved value is true, otherwise an empty string\n *\n * @example\n * ```ts\n * const text = tIf(true)`Hello World`;\n * assert.equal(text, \"Hello World\");\n * ```\n */\nexport const tIf =\n <CHECK extends Resolvable<any>>(condition: CHECK) =>\n <TValues extends Array<Text.TemplateValue.Base>>(\n template: TemplateStringsArray,\n ...args: TValues\n ): CHECK extends Promise<any>\n ? // CHECK is a promise\n Text.Return<TValues> extends Promise<any>\n ? // Text.Return<TValues> is a promise as well, so we don't need to wrap it in a promise\n Text.Return<TValues>\n : // Text.Return<TValues> is not a promise, so we need to wrap it in a promise\n Promise<Text.Return<TValues>>\n : // Neither are promises, so we don't need to wrap the return value in a promise\n Text.Return<TValues> => {\n if (!condition) return \"\" as any;\n\n if (condition instanceof Promise)\n return condition.then((c) =>\n c ? t(template, ...args) : (\"\" as any),\n ) as any;\n\n return t(template, ...args) as any;\n };\n\n/**\n * Serializes and formats a template string with values into a single string while preserving extra values typings\n * @returns The serialized and formatted string\n *\n * @features\n * - Promises even nested in arrays are resolved in parallel\n * - Array values are joined with a newline\n * - Text is trimmed\n * - Base indentation is removed\n * - Nested indentation is preserved for multi-line values\n * - Returned value is either a string or a promise of a string based on interpolated values\n * - Extra values are serialized and added to the returned value type\n *\n * @example\n * For the following template:\n * ```ts\n * const text: string = t`\n * Hello\n * ${\"- Item 1\\n- Item 2\"}\n * World\n * `;\n * ```\n *\n * The output will be:\n * ```plain\n * Hello\n * - Item 1\n * - Item 2\n * World\n * ```\n */\nexport const t = Text.t;\n\n/**\n * Deserializes a serialized string with extra values\n *\n * @param text - The text string to parse\n * @returns Array of strings and extra objects\n *\n * @example\n * ```ts\n * const text = t`Hello ${{type: \"item\", value: \"World\"}}`;\n * assert.equal(tParse(text), [\"Hello\", { type: \"item\", value: \"World\" }]);\n * ```\n */\nexport const tParse = Text.parse;\n"],"mappings":"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","names":["values"]}
|
package/package.json
CHANGED
@@ -4,7 +4,7 @@
|
|
4
4
|
"publishConfig": {
|
5
5
|
"access": "public"
|
6
6
|
},
|
7
|
-
"version": "1.1
|
7
|
+
"version": "1.2.1",
|
8
8
|
"description": "String templating as it was meant to be",
|
9
9
|
"keywords": [
|
10
10
|
"string",
|
@@ -50,19 +50,19 @@
|
|
50
50
|
}
|
51
51
|
},
|
52
52
|
"dependencies": {
|
53
|
-
"@synstack/json": "1.1.
|
54
|
-
"@synstack/resolved": "1.2.
|
55
|
-
"@synstack/str": "1.2.
|
53
|
+
"@synstack/json": "1.1.7",
|
54
|
+
"@synstack/resolved": "1.2.4",
|
55
|
+
"@synstack/str": "1.2.6"
|
56
56
|
},
|
57
57
|
"devDependencies": {
|
58
58
|
"@types/node": "^22.10.1",
|
59
59
|
"tsup": "^8.4.0",
|
60
|
-
"typescript": "^5.
|
60
|
+
"typescript": "^5.8.2"
|
61
61
|
},
|
62
62
|
"files": [
|
63
63
|
"src/**/*.ts",
|
64
64
|
"!src/**/*.test.ts",
|
65
65
|
"dist/**/*"
|
66
66
|
],
|
67
|
-
"gitHead": "
|
67
|
+
"gitHead": "4aae6e86f7664e5b928ecb3a492ec035307168d1"
|
68
68
|
}
|
package/src/text.lib.ts
CHANGED
@@ -51,10 +51,13 @@ export class Text {
|
|
51
51
|
|
52
52
|
if (Array.isArray(value)) {
|
53
53
|
wrappedValue = value
|
54
|
-
.filter(
|
54
|
+
.filter(
|
55
|
+
(inner) =>
|
56
|
+
inner !== null && inner !== undefined && inner !== false,
|
57
|
+
)
|
55
58
|
.map(Text.wrapValue)
|
56
59
|
.join(this._options.joinString);
|
57
|
-
} else {
|
60
|
+
} else if (value !== null && value !== undefined && value !== false) {
|
58
61
|
wrappedValue = Text.wrapValue(value);
|
59
62
|
}
|
60
63
|
const nextString = template[i + 1];
|
@@ -80,8 +83,8 @@ export class Text {
|
|
80
83
|
}).$ as Text.Return<T>;
|
81
84
|
}
|
82
85
|
|
83
|
-
private static wrapValue(this: void, value: Text.Value.Base) {
|
84
|
-
if (value === null || value === undefined) return "";
|
86
|
+
private static wrapValue(this: void, value: Text.Value.Base): string {
|
87
|
+
if (value === null || value === undefined || value === false) return "";
|
85
88
|
if (typeof value === "object") {
|
86
89
|
if (!Object.hasOwn(value, "type")) {
|
87
90
|
throw new Error(
|
@@ -138,7 +141,7 @@ export declare namespace Text {
|
|
138
141
|
joinString?: string;
|
139
142
|
};
|
140
143
|
|
141
|
-
export type OptionalString = string | undefined | null;
|
144
|
+
export type OptionalString = string | undefined | null | false;
|
142
145
|
|
143
146
|
export type String<TExtraObject extends Text.ExtraObject.Base = never> =
|
144
147
|
string & {
|
@@ -212,9 +215,18 @@ ${str(cause instanceof Error ? cause.message : (cause as string))
|
|
212
215
|
}
|
213
216
|
|
214
217
|
/**
|
218
|
+
* @deprecated
|
219
|
+
* Use the `&&` operator instead. `t` will filter out null, undefined, and false values.
|
220
|
+
* Conditions are prefered over `tIf` because with `tIf` the interpolated values are still executed even if the value is falsy and the condition is not met.
|
221
|
+
* `tIf` also doesn't support type narrowing to the interpolated values so this will trigger a type error:
|
222
|
+
* ```ts
|
223
|
+
* tIf(typeof value === "string")`${value}` // Likely type error as the type of value is not narrowed to a string
|
224
|
+
* ```
|
225
|
+
*
|
215
226
|
* @param condition - The condition to check, can be:
|
216
227
|
* - primitive values
|
217
228
|
* - promise of a primitive value
|
229
|
+
*
|
218
230
|
* @returns The result of the template string if the resolved value is true, otherwise an empty string
|
219
231
|
*
|
220
232
|
* @example
|