@tamagui/size 3.0.0-beta.1209.1 → 3.0.0-beta.1213.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tamagui/size",
3
- "version": "3.0.0-beta.1209.1",
3
+ "version": "3.0.0-beta.1213.1",
4
4
  "description": "Component-agnostic size context and skin table helpers",
5
5
  "license": "MIT",
6
6
  "source": "src/index.ts",
@@ -37,10 +37,10 @@
37
37
  "test": "vitest --run tests/index.test.ts"
38
38
  },
39
39
  "dependencies": {
40
- "@tamagui/web": "3.0.0-beta.1209.1"
40
+ "@tamagui/web": "3.0.0-beta.1213.1"
41
41
  },
42
42
  "devDependencies": {
43
- "@tamagui/build": "3.0.0-beta.1209.1",
43
+ "@tamagui/build": "3.0.0-beta.1213.1",
44
44
  "react": "19.2.3",
45
45
  "vitest": "4.1.0"
46
46
  },
package/src/index.ts CHANGED
@@ -99,6 +99,10 @@ const px = (value: unknown) => {
99
99
  * font 11 together. It takes `tokens.size[key]` as a minimum height rather than
100
100
  * the height, because that only describes a control under a v2-shaped size
101
101
  * scale; see the token branch.
102
+ *
103
+ * Names are checked before token keys, so naming a numeric key in `sizes` is
104
+ * the override: `sizes: { 4: { fontSize, paddingX, paddingY, radius } }` makes
105
+ * `size="$4"` a recipe you own, and nothing below is synthesized for it.
102
106
  */
103
107
  export const resolveSize = (
104
108
  value: TokenSize | null | undefined,
package/types/index.d.ts CHANGED
@@ -56,6 +56,10 @@ export type ResolvedSize = {
56
56
  * font 11 together. It takes `tokens.size[key]` as a minimum height rather than
57
57
  * the height, because that only describes a control under a v2-shaped size
58
58
  * scale; see the token branch.
59
+ *
60
+ * Names are checked before token keys, so naming a numeric key in `sizes` is
61
+ * the override: `sizes: { 4: { fontSize, paddingX, paddingY, radius } }` makes
62
+ * `size="$4"` a recipe you own, and nothing below is synthesized for it.
59
63
  */
60
64
  export declare const resolveSize: (value: TokenSize | null | undefined, env?: SizeResolverEnv) => ResolvedSize;
61
65
  /**
@@ -1,11 +1,11 @@
1
1
  {
2
- "mappings": "AAAA,cAIO,qBACA,kBACA,mBACA,eACA,iBACA,oBACA,mBACA,gBACA;AAEP,cAAc,cAAc;AAE5B,YAAY,YAAY,aAAa,0BAA0B;AAE/D,YAAY,iBAAiB,cAAc,YAAY,aAAa;CAClE,MAAM;;AAGR,YAAY,mBAAmB,cAAc,YAAY,aAAa,cACpE,iBAAiB,QACjB;AAGF,OAAO,cAAM,oBAAqB,cAAc,YAAY,WAC1D,cAAc,UACb,mBAAmB;AAItB,OAAO,cAAM,aAAa;AAE1B,YAAY,kBAAkB;CAC5B,QAAQ,KAAK,cAAc,SAAS,UAAU;;CAE9C,OAAO;CACP,QAAQ;EAAE,OAAO;kBAA6B;;CAC9C,QAAQ;;AAGV,YAAY,eAAe;;CAEzB;;CAEA;;CAEA,OAAO;EACL,4BAA4B;EAC5B,2BAA2B;EAC3B,cAAc;EACd,uBAAuB;;EAEvB,qBAAqB;;;CAGvB,MAAM;EACJ,mBAAmB;EACnB,sBAAsB;;;CAGxB;;CAEA;;;;;;;;;;;;;;;;;;AAqCF,OAAO,cAAM,cACX,OAAO,8BACP,MAAK,oBACJ;;;;;AA0HH,OAAO,cAAM,iBACX,OAAO,8BACP,OAAO",
2
+ "mappings": "AAAA,cAIO,qBACA,kBACA,mBACA,eACA,iBACA,oBACA,mBACA,gBACA;AAEP,cAAc,cAAc;AAE5B,YAAY,YAAY,aAAa,0BAA0B;AAE/D,YAAY,iBAAiB,cAAc,YAAY,aAAa;CAClE,MAAM;;AAGR,YAAY,mBAAmB,cAAc,YAAY,aAAa,cACpE,iBAAiB,QACjB;AAGF,OAAO,cAAM,oBAAqB,cAAc,YAAY,WAC1D,cAAc,UACb,mBAAmB;AAItB,OAAO,cAAM,aAAa;AAE1B,YAAY,kBAAkB;CAC5B,QAAQ,KAAK,cAAc,SAAS,UAAU;;CAE9C,OAAO;CACP,QAAQ;EAAE,OAAO;kBAA6B;;CAC9C,QAAQ;;AAGV,YAAY,eAAe;;CAEzB;;CAEA;;CAEA,OAAO;EACL,4BAA4B;EAC5B,2BAA2B;EAC3B,cAAc;EACd,uBAAuB;;EAEvB,qBAAqB;;;CAGvB,MAAM;EACJ,mBAAmB;EACnB,sBAAsB;;;CAGxB;;CAEA;;;;;;;;;;;;;;;;;;;;;;AAyCF,OAAO,cAAM,cACX,OAAO,8BACP,MAAK,oBACJ;;;;;AA0HH,OAAO,cAAM,iBACX,OAAO,8BACP,OAAO",
3
3
  "names": [],
4
4
  "sources": [
5
5
  "src/index.ts"
6
6
  ],
7
7
  "version": 3,
8
8
  "sourcesContent": [
9
- "import {\n createStyledContext,\n getConfig,\n getVariableValue,\n type FontSizeTokens,\n type GenericFont,\n type GenericSizes,\n type SizeSpec,\n type SizeTokens,\n type StyledContext,\n type TokensParsed,\n type Variable,\n} from '@tamagui/web'\n\nexport type { GenericSizes, SizeSpec }\n\nexport type TokenSize = SizeTokens | FontSizeTokens | number | true\n\nexport type SizeContextValue<Value extends TokenSize = TokenSize> = {\n size: Value | undefined\n}\n\nexport type CreatedSizeContext<Value extends TokenSize = TokenSize> = StyledContext<\n SizeContextValue<Value>,\n 'size'\n>\n\nexport const createSizeContext = <Value extends TokenSize = TokenSize>(\n defaultSize?: Value\n): CreatedSizeContext<Value> => {\n return createStyledContext<SizeContextValue<Value>>({ size: defaultSize })\n}\n\nexport const SizeContext: CreatedSizeContext = createSizeContext()\n\nexport type SizeResolverEnv = {\n tokens: Pick<TokensParsed, 'size' | 'space' | 'radius'>\n /** the component's font; a key it lacks falls back to `fonts.body` */\n font?: GenericFont\n fonts?: { body?: GenericFont; [name: string]: GenericFont | undefined }\n sizes?: GenericSizes\n}\n\nexport type ResolvedSize = {\n /** the named size, or the token key */\n name: string\n /** the font.size key the text was sized against */\n fontSizeKey: string\n /** spread onto the frame */\n frame: {\n paddingHorizontal: number | Variable\n paddingVertical?: number | Variable\n gap: number | Variable\n borderRadius: number | Variable\n /** token keys only: v2's \"size is the control height\" */\n minHeight?: number | Variable\n }\n /** spread onto the text */\n text: {\n fontSize: number | Variable\n lineHeight?: number | Variable\n }\n /** px: the recipe's icon, or the font size rounded up to the 4px grid; a token key's font size as is */\n icon: number\n /** px, without border: line-height plus vertical padding for a name, tokens.size for a key */\n controlHeight: number\n}\n\n/** outside a style pass (icons, imperative measurements): the config's default font */\nconst configEnv = (): SizeResolverEnv => {\n const conf = getConfig()\n return {\n tokens: conf.tokensParsed,\n font: conf.fontsParsed[conf.defaultFontToken],\n fonts: conf.fontsParsed,\n sizes: conf.sizes,\n }\n}\n\nlet warnedNoDefault = false\n\nconst px = (value: unknown) => {\n const n = Number.parseFloat(String(getVariableValue(value)))\n return Number.isFinite(n) ? n : 0\n}\n\n/**\n * A `size` prop is one of three things, checked in this order:\n *\n * - `true` (or nothing): the config's default named size\n * - a name in `config.sizes`: a recipe of token keys, never a height\n * - a token key like `4` or `$4`: v2's spelling, indexing every scale at key 4\n *\n * A named size never sets a height. The control ends up line-height plus\n * padding tall, so the frame, its text and its icon agree by construction.\n * Icons default to the font size rounded up to the 4px grid (12, 16, 16, 20).\n *\n * A token key keeps v2's coupling, so `size=\"$11\"` is space 11, radius 11 and\n * font 11 together. It takes `tokens.size[key]` as a minimum height rather than\n * the height, because that only describes a control under a v2-shaped size\n * scale; see the token branch.\n */\nexport const resolveSize = (\n value: TokenSize | null | undefined,\n env: SizeResolverEnv = configEnv()\n): ResolvedSize => {\n const { tokens, font: fontIn, fonts, sizes } = env\n let key: string | undefined\n if (value === true || value == null) {\n key = sizes?.default\n } else if (typeof value === 'number') {\n if (process.env.NODE_ENV === 'development') {\n console.error(\n `size={${value}} is not a control size. Use a named size (${\n Object.keys(sizes ?? {})\n .filter((k) => k !== 'default')\n .join(', ') || 'none configured'\n }) or a token key. Falling back to the default.`\n )\n }\n key = sizes?.default\n } else {\n key = String(value)\n if (key[0] === '$') key = key.slice(1)\n }\n\n if (key == null) {\n if (process.env.NODE_ENV === 'development' && !warnedNoDefault) {\n warnedNoDefault = true\n console.error(\n `No default size: pass \"sizes\" to createTamagui (see @tamagui/config/v6 \"sizes\"). Falling back to token \"4\".`\n )\n }\n key = '4'\n }\n\n let spec = key === 'default' ? undefined : sizes?.[key]\n // Try the authored key, then the configured default, then token 4. Reuse\n // one validity check and bound retries even when the default is invalid.\n for (\n let attempt = 0;\n attempt < 2 &&\n !(spec && typeof spec === 'object') &&\n tokens.size[key] == null &&\n fontIn?.size[key] == null &&\n fonts?.body?.size[key] == null;\n attempt++\n ) {\n if (process.env.NODE_ENV === 'development' && attempt === 0) {\n console.error(\n `Unknown size \"${key}\": not a named size (${\n Object.keys(sizes ?? {})\n .filter((k) => k !== 'default')\n .join(', ') || 'none configured'\n }) and not a token key. Falling back to the default.`\n )\n }\n key = attempt === 0 ? sizes?.default || '4' : '4'\n spec = key === 'default' ? undefined : sizes?.[key]\n }\n\n if (spec && typeof spec === 'object') {\n const font = fontIn?.size[spec.fontSize] != null ? fontIn : fonts?.body\n const fontSize = font?.size[spec.fontSize] ?? 0\n const lineHeight = font?.lineHeight?.[spec.fontSize]\n const paddingVertical = tokens.space[spec.paddingY]\n const fontPx = px(fontSize)\n return {\n name: key,\n fontSizeKey: spec.fontSize,\n frame: {\n paddingHorizontal: tokens.space[spec.paddingX],\n paddingVertical,\n gap: tokens.space[spec.gap ?? spec.paddingY],\n borderRadius: tokens.radius[spec.radius],\n },\n text: { fontSize, lineHeight },\n icon: spec.icon ?? Math.ceil(fontPx / 4) * 4,\n controlHeight:\n (lineHeight ? px(lineHeight) : Math.round(fontPx * 1.5)) +\n px(paddingVertical) * 2,\n }\n }\n\n // v2 read the control's height straight off `tokens.size[key]`, because under\n // v2 that scale WAS a control ramp: `size=\"$4\"` meant a 44px button. v6's size\n // scale is tailwind's spacing, where `4` is 16px, so reading a height off it\n // hands back a 16px-tall control around 20px of text, with nothing to pad it\n // out. That is the collapsed Button, and `controlSizes` used to prevent it.\n //\n // So the size token becomes a FLOOR rather than the height, and the control\n // gets vertical padding like a named size does. Under a v2-shaped scale the\n // floor is the taller of the two and nothing moves; under v6's the floor is\n // inert and the control is line-height plus padding, same as a named size.\n // Every other reading stays keyed to `key`, so v2's coupling of space, radius\n // and font at one step is intact.\n const size = tokens.size[key]\n const font = fontIn?.size[key] != null ? fontIn : fonts?.body\n const fontSize = font?.size[key]\n const lineHeight = font?.lineHeight?.[key]\n const sizePx = px(size)\n // half the horizontal space, the ratio the named ramp already uses\n const paddingVertical = Math.round(px(tokens.space[key]) / 2)\n const fontPx = px(fontSize ?? size)\n return {\n name: key,\n fontSizeKey: key,\n frame: {\n paddingHorizontal: tokens.space[key],\n paddingVertical,\n gap: Math.round(sizePx * 0.2),\n borderRadius: tokens.radius[key],\n minHeight: size,\n },\n text: { fontSize: fontSize ?? sizePx, lineHeight },\n icon: fontPx,\n controlHeight: Math.max(\n sizePx,\n (lineHeight ? px(lineHeight) : Math.round(fontPx * 1.5)) + paddingVertical * 2\n ),\n }\n}\n\n/**\n * One step smaller: the previous name in `sizes` (clamped at the smallest), or\n * for a token key the previous whole number (clamped at 1).\n */\nexport const oneSizeSmaller = (\n value: TokenSize | null | undefined,\n sizes: GenericSizes | undefined\n): string => {\n const key =\n value === true || value == null ? sizes?.default : String(value).replace(/^\\$/, '')\n if (key == null) return '3'\n const names = Object.keys(sizes ?? {}).filter((name) => name !== 'default')\n const index = names.indexOf(key)\n if (index !== -1) return names[Math.max(0, index - 1)]\n const n = Number(key)\n if (Number.isNaN(n)) return key\n return `${Math.max(1, Math.round(n) - 1)}`\n}\n"
9
+ "import {\n createStyledContext,\n getConfig,\n getVariableValue,\n type FontSizeTokens,\n type GenericFont,\n type GenericSizes,\n type SizeSpec,\n type SizeTokens,\n type StyledContext,\n type TokensParsed,\n type Variable,\n} from '@tamagui/web'\n\nexport type { GenericSizes, SizeSpec }\n\nexport type TokenSize = SizeTokens | FontSizeTokens | number | true\n\nexport type SizeContextValue<Value extends TokenSize = TokenSize> = {\n size: Value | undefined\n}\n\nexport type CreatedSizeContext<Value extends TokenSize = TokenSize> = StyledContext<\n SizeContextValue<Value>,\n 'size'\n>\n\nexport const createSizeContext = <Value extends TokenSize = TokenSize>(\n defaultSize?: Value\n): CreatedSizeContext<Value> => {\n return createStyledContext<SizeContextValue<Value>>({ size: defaultSize })\n}\n\nexport const SizeContext: CreatedSizeContext = createSizeContext()\n\nexport type SizeResolverEnv = {\n tokens: Pick<TokensParsed, 'size' | 'space' | 'radius'>\n /** the component's font; a key it lacks falls back to `fonts.body` */\n font?: GenericFont\n fonts?: { body?: GenericFont; [name: string]: GenericFont | undefined }\n sizes?: GenericSizes\n}\n\nexport type ResolvedSize = {\n /** the named size, or the token key */\n name: string\n /** the font.size key the text was sized against */\n fontSizeKey: string\n /** spread onto the frame */\n frame: {\n paddingHorizontal: number | Variable\n paddingVertical?: number | Variable\n gap: number | Variable\n borderRadius: number | Variable\n /** token keys only: v2's \"size is the control height\" */\n minHeight?: number | Variable\n }\n /** spread onto the text */\n text: {\n fontSize: number | Variable\n lineHeight?: number | Variable\n }\n /** px: the recipe's icon, or the font size rounded up to the 4px grid; a token key's font size as is */\n icon: number\n /** px, without border: line-height plus vertical padding for a name, tokens.size for a key */\n controlHeight: number\n}\n\n/** outside a style pass (icons, imperative measurements): the config's default font */\nconst configEnv = (): SizeResolverEnv => {\n const conf = getConfig()\n return {\n tokens: conf.tokensParsed,\n font: conf.fontsParsed[conf.defaultFontToken],\n fonts: conf.fontsParsed,\n sizes: conf.sizes,\n }\n}\n\nlet warnedNoDefault = false\n\nconst px = (value: unknown) => {\n const n = Number.parseFloat(String(getVariableValue(value)))\n return Number.isFinite(n) ? n : 0\n}\n\n/**\n * A `size` prop is one of three things, checked in this order:\n *\n * - `true` (or nothing): the config's default named size\n * - a name in `config.sizes`: a recipe of token keys, never a height\n * - a token key like `4` or `$4`: v2's spelling, indexing every scale at key 4\n *\n * A named size never sets a height. The control ends up line-height plus\n * padding tall, so the frame, its text and its icon agree by construction.\n * Icons default to the font size rounded up to the 4px grid (12, 16, 16, 20).\n *\n * A token key keeps v2's coupling, so `size=\"$11\"` is space 11, radius 11 and\n * font 11 together. It takes `tokens.size[key]` as a minimum height rather than\n * the height, because that only describes a control under a v2-shaped size\n * scale; see the token branch.\n *\n * Names are checked before token keys, so naming a numeric key in `sizes` is\n * the override: `sizes: { 4: { fontSize, paddingX, paddingY, radius } }` makes\n * `size=\"$4\"` a recipe you own, and nothing below is synthesized for it.\n */\nexport const resolveSize = (\n value: TokenSize | null | undefined,\n env: SizeResolverEnv = configEnv()\n): ResolvedSize => {\n const { tokens, font: fontIn, fonts, sizes } = env\n let key: string | undefined\n if (value === true || value == null) {\n key = sizes?.default\n } else if (typeof value === 'number') {\n if (process.env.NODE_ENV === 'development') {\n console.error(\n `size={${value}} is not a control size. Use a named size (${\n Object.keys(sizes ?? {})\n .filter((k) => k !== 'default')\n .join(', ') || 'none configured'\n }) or a token key. Falling back to the default.`\n )\n }\n key = sizes?.default\n } else {\n key = String(value)\n if (key[0] === '$') key = key.slice(1)\n }\n\n if (key == null) {\n if (process.env.NODE_ENV === 'development' && !warnedNoDefault) {\n warnedNoDefault = true\n console.error(\n `No default size: pass \"sizes\" to createTamagui (see @tamagui/config/v6 \"sizes\"). Falling back to token \"4\".`\n )\n }\n key = '4'\n }\n\n let spec = key === 'default' ? undefined : sizes?.[key]\n // Try the authored key, then the configured default, then token 4. Reuse\n // one validity check and bound retries even when the default is invalid.\n for (\n let attempt = 0;\n attempt < 2 &&\n !(spec && typeof spec === 'object') &&\n tokens.size[key] == null &&\n fontIn?.size[key] == null &&\n fonts?.body?.size[key] == null;\n attempt++\n ) {\n if (process.env.NODE_ENV === 'development' && attempt === 0) {\n console.error(\n `Unknown size \"${key}\": not a named size (${\n Object.keys(sizes ?? {})\n .filter((k) => k !== 'default')\n .join(', ') || 'none configured'\n }) and not a token key. Falling back to the default.`\n )\n }\n key = attempt === 0 ? sizes?.default || '4' : '4'\n spec = key === 'default' ? undefined : sizes?.[key]\n }\n\n if (spec && typeof spec === 'object') {\n const font = fontIn?.size[spec.fontSize] != null ? fontIn : fonts?.body\n const fontSize = font?.size[spec.fontSize] ?? 0\n const lineHeight = font?.lineHeight?.[spec.fontSize]\n const paddingVertical = tokens.space[spec.paddingY]\n const fontPx = px(fontSize)\n return {\n name: key,\n fontSizeKey: spec.fontSize,\n frame: {\n paddingHorizontal: tokens.space[spec.paddingX],\n paddingVertical,\n gap: tokens.space[spec.gap ?? spec.paddingY],\n borderRadius: tokens.radius[spec.radius],\n },\n text: { fontSize, lineHeight },\n icon: spec.icon ?? Math.ceil(fontPx / 4) * 4,\n controlHeight:\n (lineHeight ? px(lineHeight) : Math.round(fontPx * 1.5)) +\n px(paddingVertical) * 2,\n }\n }\n\n // v2 read the control's height straight off `tokens.size[key]`, because under\n // v2 that scale WAS a control ramp: `size=\"$4\"` meant a 44px button. v6's size\n // scale is tailwind's spacing, where `4` is 16px, so reading a height off it\n // hands back a 16px-tall control around 20px of text, with nothing to pad it\n // out. That is the collapsed Button, and `controlSizes` used to prevent it.\n //\n // So the size token becomes a FLOOR rather than the height, and the control\n // gets vertical padding like a named size does. Under a v2-shaped scale the\n // floor is the taller of the two and nothing moves; under v6's the floor is\n // inert and the control is line-height plus padding, same as a named size.\n // Every other reading stays keyed to `key`, so v2's coupling of space, radius\n // and font at one step is intact.\n const size = tokens.size[key]\n const font = fontIn?.size[key] != null ? fontIn : fonts?.body\n const fontSize = font?.size[key]\n const lineHeight = font?.lineHeight?.[key]\n const sizePx = px(size)\n // half the horizontal space, the ratio the named ramp already uses\n const paddingVertical = Math.round(px(tokens.space[key]) / 2)\n const fontPx = px(fontSize ?? size)\n return {\n name: key,\n fontSizeKey: key,\n frame: {\n paddingHorizontal: tokens.space[key],\n paddingVertical,\n gap: Math.round(sizePx * 0.2),\n borderRadius: tokens.radius[key],\n minHeight: size,\n },\n text: { fontSize: fontSize ?? sizePx, lineHeight },\n icon: fontPx,\n controlHeight: Math.max(\n sizePx,\n (lineHeight ? px(lineHeight) : Math.round(fontPx * 1.5)) + paddingVertical * 2\n ),\n }\n}\n\n/**\n * One step smaller: the previous name in `sizes` (clamped at the smallest), or\n * for a token key the previous whole number (clamped at 1).\n */\nexport const oneSizeSmaller = (\n value: TokenSize | null | undefined,\n sizes: GenericSizes | undefined\n): string => {\n const key =\n value === true || value == null ? sizes?.default : String(value).replace(/^\\$/, '')\n if (key == null) return '3'\n const names = Object.keys(sizes ?? {}).filter((name) => name !== 'default')\n const index = names.indexOf(key)\n if (index !== -1) return names[Math.max(0, index - 1)]\n const n = Number(key)\n if (Number.isNaN(n)) return key\n return `${Math.max(1, Math.round(n) - 1)}`\n}\n"
10
10
  ]
11
11
  }