@neko-os/ui 0.5.3 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (159) hide show
  1. package/dist/NekoUI.js +12 -9
  2. package/dist/abstractions/Image.native.js +1 -1
  3. package/dist/abstractions/Image.web.js +1 -1
  4. package/dist/abstractions/WindowOverlay.js +3 -0
  5. package/dist/abstractions/WindowOverlay.native.js +21 -0
  6. package/dist/abstractions/helpers/storage.js +14 -4
  7. package/dist/abstractions/helpers/storage.native.js +9 -1
  8. package/dist/components/feedback/notifications/NotificationsHandler.js +10 -6
  9. package/dist/components/form/useNewForm.js +2 -0
  10. package/dist/components/index.js +3 -1
  11. package/dist/components/inputs/DateInput.js +10 -6
  12. package/dist/components/inputs/InputWrapper.js +11 -11
  13. package/dist/components/inputs/NumberWheelInput.js +50 -0
  14. package/dist/components/inputs/NumberWheelPicker.js +43 -0
  15. package/dist/components/inputs/SegmentedPicker.js +3 -2
  16. package/dist/components/inputs/UploadInput.js +4 -4
  17. package/dist/components/inputs/WheelPicker.js +49 -0
  18. package/dist/components/inputs/WheelPicker.native.js +88 -0
  19. package/dist/components/inputs/WheelPicker.web.js +1 -0
  20. package/dist/components/inputs/dateWheelPicker/DateWheelPicker.js +24 -0
  21. package/dist/components/inputs/dateWheelPicker/DayWheelPicker.js +48 -0
  22. package/dist/components/inputs/dateWheelPicker/MonthWheelPicker.js +19 -0
  23. package/dist/components/inputs/dateWheelPicker/QuarterWheelPicker.js +61 -0
  24. package/dist/components/inputs/dateWheelPicker/WeekWheelPicker.js +66 -0
  25. package/dist/components/inputs/dateWheelPicker/YearWheelPicker.js +35 -0
  26. package/dist/components/inputs/index.js +5 -1
  27. package/dist/components/inputs/upload/Upload.native.js +60 -52
  28. package/dist/components/inputs/upload/useUploadState.js +11 -3
  29. package/dist/components/measurements/FeetInchesInput.js +91 -0
  30. package/dist/components/measurements/LengthInput.js +32 -0
  31. package/dist/components/measurements/LengthText.js +10 -0
  32. package/dist/components/measurements/MeasurementHandler.js +26 -0
  33. package/dist/components/measurements/WeightInput.js +25 -0
  34. package/dist/components/measurements/WeightText.js +10 -0
  35. package/dist/components/measurements/helpers/detectMeasurementSystem.js +15 -0
  36. package/dist/components/measurements/helpers/detectMeasurementSystem.native.js +9 -0
  37. package/dist/components/measurements/helpers/index.js +2 -0
  38. package/dist/components/measurements/helpers/length.js +112 -0
  39. package/dist/components/measurements/helpers/weight.js +56 -0
  40. package/dist/components/measurements/index.js +9 -0
  41. package/dist/components/measurements/useLengthFormatter.js +35 -0
  42. package/dist/components/measurements/useLocalInputValue.js +32 -0
  43. package/dist/components/measurements/useWeightFormatter.js +29 -0
  44. package/dist/components/presentation/Avatar.js +3 -3
  45. package/dist/components/routing/ReturnButton.js +20 -0
  46. package/dist/components/routing/ReturnButton.native.js +20 -0
  47. package/dist/components/routing/ReturnButton.web.js +2 -0
  48. package/dist/components/routing/ReturnLink.js +25 -0
  49. package/dist/components/routing/ReturnLink.native.js +25 -0
  50. package/dist/components/routing/ReturnLink.web.js +2 -0
  51. package/dist/components/routing/RoutedStepsContent.js +21 -0
  52. package/dist/components/routing/RoutedStepsContent.native.js +94 -0
  53. package/dist/components/routing/RoutedStepsContent.web.js +3 -0
  54. package/dist/components/routing/index.js +3 -0
  55. package/dist/components/state/StatePresenter.js +1 -1
  56. package/dist/components/steps/StepsHandler.js +2 -0
  57. package/dist/components/structure/TopBar.js +18 -16
  58. package/dist/components/theme/ThemePickerDrawer.js +1 -1
  59. package/dist/helpers/compress.js +61 -0
  60. package/dist/helpers/compress.native.js +49 -0
  61. package/dist/helpers/files.js +7 -0
  62. package/dist/helpers/files.native.js +55 -0
  63. package/dist/helpers/index.js +6 -1
  64. package/dist/helpers/media.js +4 -0
  65. package/dist/helpers/media.native.js +41 -0
  66. package/dist/helpers/numbers.js +13 -0
  67. package/dist/helpers/pickAssets.js +7 -0
  68. package/dist/helpers/pickAssets.native.js +66 -0
  69. package/dist/helpers/storage.js +17 -0
  70. package/dist/i18n/I18n.js +4 -4
  71. package/dist/index.js +1 -1
  72. package/dist/modifiers/flex.js +8 -3
  73. package/dist/responsive/responsiveHooks.js +14 -0
  74. package/dist/theme/default/blackTheme.js +3 -1
  75. package/dist/theme/default/cyberpunkTheme.js +3 -1
  76. package/dist/theme/default/darkTheme.js +3 -1
  77. package/dist/theme/default/hackerTheme.js +3 -1
  78. package/dist/theme/default/lightTheme.js +3 -1
  79. package/dist/theme/default/paperTheme.js +3 -1
  80. package/package.json +2 -14
  81. package/src/NekoUI.js +16 -13
  82. package/src/abstractions/Image.native.js +1 -1
  83. package/src/abstractions/Image.web.js +1 -1
  84. package/src/abstractions/WindowOverlay.js +3 -0
  85. package/src/abstractions/WindowOverlay.native.js +21 -0
  86. package/src/abstractions/helpers/storage.js +13 -3
  87. package/src/abstractions/helpers/storage.native.js +8 -0
  88. package/src/components/feedback/notifications/NotificationsHandler.js +12 -8
  89. package/src/components/form/useNewForm.js +2 -0
  90. package/src/components/index.js +2 -0
  91. package/src/components/inputs/DateInput.js +8 -4
  92. package/src/components/inputs/InputWrapper.js +3 -3
  93. package/src/components/inputs/NumberWheelInput.js +50 -0
  94. package/src/components/inputs/NumberWheelPicker.js +43 -0
  95. package/src/components/inputs/SegmentedPicker.js +2 -1
  96. package/src/components/inputs/UploadInput.js +2 -2
  97. package/src/components/inputs/WheelPicker.js +49 -0
  98. package/src/components/inputs/WheelPicker.native.js +88 -0
  99. package/src/components/inputs/WheelPicker.web.js +1 -0
  100. package/src/components/inputs/dateWheelPicker/DateWheelPicker.js +24 -0
  101. package/src/components/inputs/dateWheelPicker/DayWheelPicker.js +48 -0
  102. package/src/components/inputs/dateWheelPicker/MonthWheelPicker.js +19 -0
  103. package/src/components/inputs/dateWheelPicker/QuarterWheelPicker.js +61 -0
  104. package/src/components/inputs/dateWheelPicker/WeekWheelPicker.js +66 -0
  105. package/src/components/inputs/dateWheelPicker/YearWheelPicker.js +35 -0
  106. package/src/components/inputs/index.js +4 -0
  107. package/src/components/inputs/upload/Upload.native.js +58 -50
  108. package/src/components/inputs/upload/useUploadState.js +11 -3
  109. package/src/components/measurements/FeetInchesInput.js +91 -0
  110. package/src/components/measurements/LengthInput.js +32 -0
  111. package/src/components/measurements/LengthText.js +10 -0
  112. package/src/components/measurements/MeasurementHandler.js +26 -0
  113. package/src/components/measurements/WeightInput.js +25 -0
  114. package/src/components/measurements/WeightText.js +10 -0
  115. package/src/components/measurements/helpers/detectMeasurementSystem.js +15 -0
  116. package/src/components/measurements/helpers/detectMeasurementSystem.native.js +9 -0
  117. package/src/components/measurements/helpers/index.js +2 -0
  118. package/src/components/measurements/helpers/length.js +112 -0
  119. package/src/components/measurements/helpers/weight.js +56 -0
  120. package/src/components/measurements/index.js +9 -0
  121. package/src/components/measurements/useLengthFormatter.js +35 -0
  122. package/src/components/measurements/useLocalInputValue.js +32 -0
  123. package/src/components/measurements/useWeightFormatter.js +29 -0
  124. package/src/components/presentation/Avatar.js +2 -2
  125. package/src/components/routing/ReturnButton.js +20 -0
  126. package/src/components/routing/ReturnButton.native.js +20 -0
  127. package/src/components/routing/ReturnButton.web.js +2 -0
  128. package/src/components/routing/ReturnLink.js +25 -0
  129. package/src/components/routing/ReturnLink.native.js +25 -0
  130. package/src/components/routing/ReturnLink.web.js +2 -0
  131. package/src/components/routing/RoutedStepsContent.js +21 -0
  132. package/src/components/routing/RoutedStepsContent.native.js +94 -0
  133. package/src/components/routing/RoutedStepsContent.web.js +3 -0
  134. package/src/components/routing/index.js +3 -0
  135. package/src/components/state/StatePresenter.js +1 -1
  136. package/src/components/steps/StepsHandler.js +2 -0
  137. package/src/components/structure/TopBar.js +16 -14
  138. package/src/components/theme/ThemePickerDrawer.js +1 -1
  139. package/src/helpers/compress.js +61 -0
  140. package/src/helpers/compress.native.js +49 -0
  141. package/src/helpers/files.js +7 -0
  142. package/src/helpers/files.native.js +55 -0
  143. package/src/helpers/index.js +6 -1
  144. package/src/helpers/media.js +4 -0
  145. package/src/helpers/media.native.js +41 -0
  146. package/src/helpers/numbers.js +13 -0
  147. package/src/helpers/pickAssets.js +7 -0
  148. package/src/helpers/pickAssets.native.js +66 -0
  149. package/src/helpers/storage.js +17 -0
  150. package/src/i18n/I18n.js +2 -2
  151. package/src/index.js +1 -1
  152. package/src/modifiers/flex.js +7 -2
  153. package/src/responsive/responsiveHooks.js +14 -0
  154. package/src/theme/default/blackTheme.js +2 -0
  155. package/src/theme/default/cyberpunkTheme.js +2 -0
  156. package/src/theme/default/darkTheme.js +2 -0
  157. package/src/theme/default/hackerTheme.js +2 -0
  158. package/src/theme/default/lightTheme.js +2 -0
  159. package/src/theme/default/paperTheme.js +2 -0
@@ -0,0 +1,61 @@
1
+ const IMAGE_DEFAULTS = { maxWidth: 1920, maxHeight: 1920, quality: 0.8 }
2
+
3
+ function isImage(asset) {
4
+ return asset?.type?.startsWith('image/')
5
+ }
6
+
7
+ function loadImage(src) {
8
+ return new Promise((resolve, reject) => {
9
+ const img = new Image()
10
+ img.onload = () => resolve(img)
11
+ img.onerror = reject
12
+ img.src = src
13
+ })
14
+ }
15
+
16
+ const ALPHA_TYPES = new Set(['image/png', 'image/webp', 'image/gif'])
17
+
18
+ function compressWithCanvas(img, { maxWidth, maxHeight, quality, mimeType }) {
19
+ let { width, height } = img
20
+ if (width > maxWidth || height > maxHeight) {
21
+ const ratio = Math.min(maxWidth / width, maxHeight / height)
22
+ width = Math.round(width * ratio)
23
+ height = Math.round(height * ratio)
24
+ }
25
+
26
+ const canvas = document.createElement('canvas')
27
+ canvas.width = width
28
+ canvas.height = height
29
+ const ctx = canvas.getContext('2d')
30
+ ctx.drawImage(img, 0, 0, width, height)
31
+
32
+ const outputType = ALPHA_TYPES.has(mimeType) ? mimeType : 'image/jpeg'
33
+
34
+ return new Promise((resolve) => {
35
+ canvas.toBlob(
36
+ (blob) => resolve(blob ? { uri: URL.createObjectURL(blob), width, height } : null),
37
+ outputType,
38
+ quality
39
+ )
40
+ })
41
+ }
42
+
43
+ export async function compressAsset(asset, options = {}) {
44
+ if (!asset?.uri || !isImage(asset)) return asset
45
+
46
+ try {
47
+ const opts = { ...IMAGE_DEFAULTS, ...options.image }
48
+ const img = await loadImage(asset.uri)
49
+ const result = await compressWithCanvas(img, { ...opts, mimeType: asset.type })
50
+ if (!result) return asset
51
+ return { ...asset, uri: result.uri, width: result.width, height: result.height }
52
+ } catch (e) {
53
+ console.warn('[neko-ui compress] web image compression failed:', e?.message)
54
+ return asset
55
+ }
56
+ }
57
+
58
+ export function compressAssets(assets, options = {}) {
59
+ if (!assets?.length) return Promise.resolve(assets || [])
60
+ return Promise.all(assets.map((a) => compressAsset(a, options)))
61
+ }
@@ -0,0 +1,49 @@
1
+ let ImageCompressor
2
+ let VideoCompressor
3
+ try {
4
+ const RNC = require('react-native-compressor')
5
+ ImageCompressor = RNC.Image
6
+ VideoCompressor = RNC.Video
7
+ } catch {}
8
+
9
+ const IMAGE_DEFAULTS = { maxWidth: 1920, maxHeight: 1920, quality: 0.8 }
10
+ const VIDEO_DEFAULTS = { maxSize: 720 }
11
+
12
+ function isImage(asset) {
13
+ return asset?.type?.startsWith('image/')
14
+ }
15
+
16
+ function isVideo(asset) {
17
+ return asset?.type?.startsWith('video/')
18
+ }
19
+
20
+ export async function compressAsset(asset, options = {}) {
21
+ if (!asset?.uri) return asset
22
+
23
+ if (isImage(asset) && ImageCompressor) {
24
+ try {
25
+ const uri = await ImageCompressor.compress(asset.uri, { ...IMAGE_DEFAULTS, ...options.image })
26
+ return { ...asset, uri }
27
+ } catch (e) {
28
+ console.warn('[neko-ui compress] image failed, keeping original:', e?.message)
29
+ return asset
30
+ }
31
+ }
32
+
33
+ if (isVideo(asset) && VideoCompressor) {
34
+ try {
35
+ const uri = await VideoCompressor.compress(asset.uri, { ...VIDEO_DEFAULTS, ...options.video })
36
+ return { ...asset, uri }
37
+ } catch (e) {
38
+ console.warn('[neko-ui compress] video failed, keeping original:', e?.message)
39
+ return asset
40
+ }
41
+ }
42
+
43
+ return asset
44
+ }
45
+
46
+ export function compressAssets(assets, options = {}) {
47
+ if (!assets?.length) return Promise.resolve(assets || [])
48
+ return Promise.all(assets.map((a) => compressAsset(a, options)))
49
+ }
@@ -0,0 +1,7 @@
1
+ // Web / non-native: no filesystem persistence. The uri (blob/object URL or
2
+ // remote) is used as-is. Mirrors the native API surface.
3
+ export function persistFile(uri) {
4
+ return uri
5
+ }
6
+
7
+ export function removeFile() {}
@@ -0,0 +1,55 @@
1
+ let FS
2
+ try {
3
+ FS = require('expo-file-system')
4
+ } catch {}
5
+
6
+ // `target="<base>/<subdir>"`, base is 'document' | 'cache'. Resolves to a
7
+ // permanent Directory (created if missing) via the modern sync expo-file-system API.
8
+ function resolveDir(target) {
9
+ const [base, ...rest] = (target || '').split('/').filter(Boolean)
10
+ if (base !== 'cache' && base !== 'document') {
11
+ console.warn(`[neko-ui files] target base "${base}" unknown — using document dir. Use "document" or "cache".`)
12
+ }
13
+ const root = base === 'cache' ? FS.Paths.cache : FS.Paths.document
14
+ const dir = new FS.Directory(root, ...rest)
15
+ try {
16
+ dir.create({ intermediates: true, idempotent: true })
17
+ } catch (e) {
18
+ console.warn('[neko-ui files] dir create failed:', e?.message)
19
+ }
20
+ return dir
21
+ }
22
+
23
+ let seq = 0
24
+
25
+ // Copy a file at `uri` into a permanent app directory (`target`, default
26
+ // 'document/files'). Returns the durable uri string. Never throws — on failure
27
+ // or when expo-file-system is unavailable, returns the original uri.
28
+ // `opts.name` is used only to derive the file extension (falls back to the uri,
29
+ // then 'jpg'); the stored filename is always unique (timestamp + counter), so
30
+ // rapid multi-select can't collide. Expects a `file://` source (e.g.
31
+ // expo-image-picker output); `content://` sources can't be copied and fall back
32
+ // to the original uri.
33
+ export function persistFile(uri, target = 'document/files', { name } = {}) {
34
+ if (!FS || !uri) return uri
35
+ try {
36
+ const ext = name?.split('.').pop() || uri.split('?')[0].split('.').pop() || 'jpg'
37
+ const filename = `${Date.now()}_${seq++}_${Math.round(Math.random() * 1e6)}.${ext}`
38
+ const dest = new FS.File(resolveDir(target), filename)
39
+ new FS.File(uri).copy(dest)
40
+ return dest.uri
41
+ } catch (e) {
42
+ console.warn('[neko-ui files] persistFile failed, keeping uri:', e?.message)
43
+ return uri
44
+ }
45
+ }
46
+
47
+ // Delete a persisted file. No-op if missing / unavailable.
48
+ export function removeFile(uri) {
49
+ if (!FS || !uri) return
50
+ try {
51
+ new FS.File(uri).delete()
52
+ } catch (e) {
53
+ console.warn('[neko-ui files] removeFile failed:', e?.message)
54
+ }
55
+ }
@@ -1,7 +1,12 @@
1
+ export * from './compress'
1
2
  export * from './debounce'
2
- export * from './string'
3
+ export * from './files'
4
+ export * from './media'
5
+ export * from './numbers'
6
+ export * from './pickAssets'
3
7
  export * from './random'
4
8
  export * from './storage'
9
+ export * from './string'
5
10
  export * from './weekStart'
6
11
  export * from './weekStartSetup'
7
12
  export * from './../abstractions/helpers/useSafeAreaInsets'
@@ -0,0 +1,4 @@
1
+ // Web / non-native: native camera & library pickers aren't available here.
2
+ // (Upload's web implementation uses its own file-input flow.)
3
+ export const openCamera = async () => []
4
+ export const openLibrary = async () => []
@@ -0,0 +1,41 @@
1
+ let ImagePicker
2
+ try {
3
+ ImagePicker = require('expo-image-picker')
4
+ } catch {}
5
+
6
+ // Normalize an expo-image-picker asset into the shape neko-ui works with.
7
+ // Internal — callers consume the already-normalized assets from openCamera/openLibrary.
8
+ function normalizeImageResult(asset) {
9
+ return {
10
+ uri: asset.uri,
11
+ name: asset.fileName || asset.uri.split('/').pop(),
12
+ type: asset.mimeType || asset.type || 'image/jpeg',
13
+ size: asset.fileSize || asset.filesize,
14
+ width: asset.width,
15
+ height: asset.height,
16
+ }
17
+ }
18
+
19
+ // Request camera permission, launch the camera, return normalized assets.
20
+ // Returns [] when expo-image-picker is missing, permission is denied, or the user
21
+ // cancels. The caller owns post-processing (persist, save-to-library, closing any
22
+ // drawer) — and any drawer should stay open until this resolves (iOS present/
23
+ // dismiss collision otherwise).
24
+ export async function openCamera(options = {}) {
25
+ if (!ImagePicker) return []
26
+ const permission = await ImagePicker.requestCameraPermissionsAsync()
27
+ if (!permission.granted) return []
28
+ const result = await ImagePicker.launchCameraAsync(options)
29
+ if (result.canceled) return []
30
+ return result.assets.map(normalizeImageResult)
31
+ }
32
+
33
+ // Same as openCamera but for the photo library.
34
+ export async function openLibrary(options = {}) {
35
+ if (!ImagePicker) return []
36
+ const permission = await ImagePicker.requestMediaLibraryPermissionsAsync()
37
+ if (!permission.granted) return []
38
+ const result = await ImagePicker.launchImageLibraryAsync(options)
39
+ if (result.canceled) return []
40
+ return result.assets.map(normalizeImageResult)
41
+ }
@@ -0,0 +1,13 @@
1
+ import { is } from 'ramda'
2
+
3
+ export function fixedDecimals(num, count = 2) {
4
+ if (!num) return num
5
+ if (Number.isInteger(num)) return num
6
+ if (is(String, num)) num = parseFloat(num)
7
+
8
+ const decimalPart = num.toString().split('.')[1]
9
+ if (decimalPart && decimalPart.length > count) {
10
+ return parseFloat(num.toFixed(count))
11
+ }
12
+ return num
13
+ }
@@ -0,0 +1,7 @@
1
+ export async function pickFromCamera({ multiple = false } = {}) {
2
+ return multiple ? [] : null
3
+ }
4
+
5
+ export async function pickFromLibrary({ multiple = false } = {}) {
6
+ return multiple ? [] : null
7
+ }
@@ -0,0 +1,66 @@
1
+ import { openCamera, openLibrary } from './media'
2
+ import { compressAsset, compressAssets } from './compress'
3
+ import { persistFile } from './files'
4
+
5
+ let MediaLibrary
6
+ try {
7
+ MediaLibrary = require('expo-media-library')
8
+ } catch {}
9
+
10
+ async function saveAssetsToLibrary(assets) {
11
+ if (!MediaLibrary || !assets?.length) return
12
+ try {
13
+ const perm = await MediaLibrary.requestPermissionsAsync(true)
14
+ if (!perm.granted) return
15
+ for (const a of assets) {
16
+ try {
17
+ await MediaLibrary.saveToLibraryAsync(a.uri)
18
+ } catch (e) {
19
+ console.warn('[neko-ui pick] saveToLibrary failed:', e?.message)
20
+ }
21
+ }
22
+ } catch (e) {
23
+ console.warn('[neko-ui pick] saveToLibrary permission error:', e?.message)
24
+ }
25
+ }
26
+
27
+ function persistAsset(asset, persistTo) {
28
+ if (!persistTo) return asset
29
+ return { ...asset, uri: persistFile(asset.uri, persistTo, { name: asset.name }) }
30
+ }
31
+
32
+ async function processAssets(assets, { compress = true, persistTo } = {}) {
33
+ let result = assets
34
+ if (compress !== false) {
35
+ const compressOpts = typeof compress === 'object' ? compress : {}
36
+ result = await compressAssets(result, compressOpts)
37
+ }
38
+ if (persistTo) {
39
+ result = result.map((a) => persistAsset(a, persistTo))
40
+ }
41
+ return result
42
+ }
43
+
44
+ export async function pickFromCamera({ multiple = false, maxCount, mediaTypes, persistTo, compress, saveToLibrary } = {}) {
45
+ const assets = await openCamera({
46
+ allowsMultipleSelection: multiple,
47
+ selectionLimit: maxCount || 0,
48
+ mediaTypes,
49
+ })
50
+ if (!assets.length) return multiple ? [] : null
51
+ // Save original (pre-compression) captures to device Photos
52
+ if (saveToLibrary) await saveAssetsToLibrary(assets)
53
+ const result = await processAssets(assets, { compress, persistTo })
54
+ return multiple ? result : result[0]
55
+ }
56
+
57
+ export async function pickFromLibrary({ multiple = false, maxCount, mediaTypes, persistTo, compress } = {}) {
58
+ const assets = await openLibrary({
59
+ allowsMultipleSelection: multiple,
60
+ selectionLimit: maxCount || 0,
61
+ mediaTypes,
62
+ })
63
+ if (!assets.length) return multiple ? [] : null
64
+ const result = await processAssets(assets, { compress, persistTo })
65
+ return multiple ? result : result[0]
66
+ }
@@ -41,6 +41,21 @@ function getAsync(key, defaultValue) {
41
41
  })
42
42
  }
43
43
 
44
+ function notifyAll(value) {
45
+ Object.keys(listeners).forEach((key) => notify(key, value))
46
+ }
47
+
48
+ function clear() {
49
+ AbsStorage.clear()
50
+ notifyAll(undefined)
51
+ }
52
+
53
+ function clearAsync() {
54
+ return AbsStorage.clearAsync().then(() => {
55
+ notifyAll(undefined)
56
+ })
57
+ }
58
+
44
59
  function formatStoragedValue(value) {
45
60
  try {
46
61
  if (!value) return value
@@ -73,5 +88,7 @@ export const Storage = {
73
88
  setAsync,
74
89
  get,
75
90
  getAsync,
91
+ clear,
92
+ clearAsync,
76
93
  useState,
77
94
  }
package/src/i18n/I18n.js CHANGED
@@ -26,7 +26,7 @@ export class I18n {
26
26
  }
27
27
 
28
28
  t(key, opts = {}) {
29
- let { ns = 'common', context, ...vars } = opts
29
+ let { ns = 'common', context, default: defaultVal, ...vars } = opts
30
30
  const count = vars?.count
31
31
  if (key.includes(':')) {
32
32
  const splittedKey = key.split(':')
@@ -39,7 +39,7 @@ export class I18n {
39
39
 
40
40
  if (!value) {
41
41
  const fallbackData = this.resources[this.fallback]?.[ns]
42
- value = this._resolveKey(fallbackData, key, count, context) || key
42
+ value = this._resolveKey(fallbackData, key, count, context) ?? defaultVal ?? key
43
43
  }
44
44
 
45
45
  return this._interpolate(value, vars)
package/src/index.js CHANGED
@@ -6,4 +6,4 @@ export * from './i18n'
6
6
  export * from './NekoUI'
7
7
  export * from './abstractions'
8
8
 
9
- export const version = 41
9
+ export const version = 44
@@ -1,11 +1,16 @@
1
1
  import { clearProps, flattenStyle } from './_helpers'
2
2
 
3
3
  export function useFlexModifier([values, props]) {
4
- let { flex, ...restProps } = props
4
+ let { flex, alignSelf, selfStretch, selfCenter, selfStart, selfEnd, ...restProps } = props
5
5
 
6
6
  if (flex === true) flex = 1
7
7
 
8
- const style = clearProps({ flex, minWidth: 0 })
8
+ if (selfStretch) alignSelf = 'stretch'
9
+ if (selfCenter) alignSelf = 'center'
10
+ if (selfStart) alignSelf = 'flex-start'
11
+ if (selfEnd) alignSelf = 'flex-end'
12
+
13
+ const style = clearProps({ flex, minWidth: 0, alignSelf })
9
14
 
10
15
  return [
11
16
  values,
@@ -27,6 +27,20 @@ export function useGetResponsiveValue() {
27
27
 
28
28
  if (!isObj) return value
29
29
 
30
+ // Only treat the object as a responsive descriptor when it actually
31
+ // carries breakpoint / platform / df keys. Otherwise it is a plain
32
+ // object prop (e.g. titleProps) and must pass through untouched.
33
+ const bpNames = breakpoints.map((b) => b.name)
34
+ const isResponsiveKey = (k) =>
35
+ k === 'df' ||
36
+ k === 'native' ||
37
+ k === 'web' ||
38
+ k === 'ios' ||
39
+ k === 'android' ||
40
+ bpNames.includes(k) ||
41
+ (/^(\w+)[du]$/.test(k) && bpNames.includes(k.slice(0, -1)))
42
+ if (!Object.keys(value).some(isResponsiveKey)) return value
43
+
30
44
  if (value[screen]) return value[screen]
31
45
 
32
46
  const keys = Object.keys(value)
@@ -33,6 +33,8 @@ export const DEFAULT_BLACK_THEME = mergeDeepRight(BASE_THEME, {
33
33
  brown: '#8B5E3C',
34
34
  lylac: '#C77DFF',
35
35
  pink: '#FF6FAE',
36
+ white: '#FFFFFF',
37
+ black: '#000000',
36
38
  },
37
39
 
38
40
  // components: {
@@ -32,5 +32,7 @@ export const CYBERPUNK_DARK_THEME = mergeDeepRight(BASE_THEME, {
32
32
  brown: '#8B5CF6',
33
33
  lylac: '#C77DFF',
34
34
  pink: '#FF4FD8',
35
+ white: '#FFFFFF',
36
+ black: '#000000',
35
37
  },
36
38
  })
@@ -31,5 +31,7 @@ export const DEFAULT_DARK_THEME = mergeDeepRight(BASE_THEME, {
31
31
  brown: '#8D6E63',
32
32
  lylac: '#B39DDB',
33
33
  pink: '#F48FB1',
34
+ white: '#FFFFFF',
35
+ black: '#000000',
34
36
  },
35
37
  })
@@ -39,6 +39,8 @@ export const DEFAULT_MATRIX_THEME = mergeDeepRight(BASE_THEME, {
39
39
  brown: '#5C4033',
40
40
  lylac: '#C084FC',
41
41
  pink: '#FF4FD8',
42
+ white: '#FFFFFF',
43
+ black: '#000000',
42
44
  },
43
45
 
44
46
  radius: {
@@ -31,5 +31,7 @@ export const DEFAULT_LIGHT_THEME = mergeDeepRight(BASE_THEME, {
31
31
  brown: '#8D6E63',
32
32
  lylac: '#B39DDB',
33
33
  pink: '#F48FB1',
34
+ white: '#FFFFFF',
35
+ black: '#000000',
34
36
  },
35
37
  })
@@ -32,5 +32,7 @@ export const DEFAULT_PAPER_THEME = mergeDeepRight(BASE_THEME, {
32
32
  brown: '#8B7355',
33
33
  lylac: '#C7B7D4',
34
34
  pink: '#E4A1B2',
35
+ white: '#FFFFFF',
36
+ black: '#000000',
35
37
  },
36
38
  })