react-native-tinyui 0.4.0 → 1.0.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.
- package/README.md +383 -53
- package/ios/ConcentricView/TinyuiConcentricView.h +12 -0
- package/ios/ConcentricView/TinyuiConcentricView.mm +60 -0
- package/ios/Core/Tinyui.mm +54 -0
- package/ios/Core/TinyuiComponentClasses.mm +44 -0
- package/ios/LiquidGlassText/TinyuiLiquidGlassTextProvider.swift +64 -79
- package/ios/LiquidGlassText/TinyuiLiquidGlassTextShadowNode.h +46 -0
- package/ios/LiquidGlassText/TinyuiLiquidGlassTextShadowNode.mm +159 -0
- package/ios/LiquidGlassText/TinyuiLiquidGlassTextView.mm +6 -27
- package/ios/LiquidGlassText/TinyuiTextOutlineShape.swift +5 -2
- package/ios/Menu/TinyuiMenuBuilder.swift +238 -0
- package/ios/Menu/TinyuiMenuProvider.swift +51 -85
- package/ios/Menu/TinyuiMenuView.mm +13 -35
- package/ios/Popover/TinyuiPopoverProvider.swift +198 -44
- package/ios/Popover/TinyuiPopoverView.mm +26 -1
- package/ios/SFSymbol/TinyuiSFSymbolEffects.h +9 -0
- package/ios/SFSymbol/TinyuiSFSymbolEffects.mm +180 -0
- package/ios/SFSymbol/TinyuiSFSymbolImage.h +8 -0
- package/ios/SFSymbol/TinyuiSFSymbolImage.mm +141 -0
- package/ios/SFSymbol/TinyuiSFSymbolShadowNode.h +31 -0
- package/ios/SFSymbol/TinyuiSFSymbolShadowNode.mm +30 -0
- package/ios/SFSymbol/TinyuiSFSymbolView.h +4 -0
- package/ios/SFSymbol/TinyuiSFSymbolView.mm +173 -0
- package/ios/Stepper/TinyuiStepperShadowNode.h +32 -0
- package/ios/Stepper/TinyuiStepperShadowNode.mm +28 -0
- package/ios/Stepper/TinyuiStepperView.h +12 -0
- package/ios/Stepper/TinyuiStepperView.mm +128 -0
- package/ios/TipKit/TinyuiTip.swift +31 -0
- package/ios/TipKit/TinyuiTipPopoverProvider.swift +276 -0
- package/ios/TipKit/TinyuiTipPopoverView.h +8 -0
- package/ios/TipKit/TinyuiTipPopoverView.mm +114 -0
- package/ios/TipKit/TinyuiTipsRuntime.swift +148 -0
- package/lib/module/ConcentricView/ConcentricViewNativeComponent.ts +14 -0
- package/lib/module/ConcentricView/index.js +29 -0
- package/lib/module/ConcentricView/index.js.map +1 -0
- package/lib/module/ConcentricView/types.js +4 -0
- package/lib/module/ConcentricView/types.js.map +1 -0
- package/lib/module/LiquidGlassText/LiquidGlassTextNativeComponent.ts +2 -12
- package/lib/module/LiquidGlassText/index.js +60 -35
- package/lib/module/LiquidGlassText/index.js.map +1 -1
- package/lib/module/Menu/MenuNativeComponent.ts +7 -5
- package/lib/module/Menu/index.js +4 -9
- package/lib/module/Menu/index.js.map +1 -1
- package/lib/module/Menu/options.js +4 -12
- package/lib/module/Menu/options.js.map +1 -1
- package/lib/module/NativeTinyui.js.map +1 -1
- package/lib/module/Popover/index.js +4 -2
- package/lib/module/Popover/index.js.map +1 -1
- package/lib/module/SFSymbol/SFSymbolNativeComponent.ts +20 -0
- package/lib/module/SFSymbol/index.js +73 -0
- package/lib/module/SFSymbol/index.js.map +1 -0
- package/lib/module/SFSymbol/options.js +79 -0
- package/lib/module/SFSymbol/options.js.map +1 -0
- package/lib/module/SFSymbol/types.js +4 -0
- package/lib/module/SFSymbol/types.js.map +1 -0
- package/lib/module/Stepper/StepperNativeComponent.ts +29 -0
- package/lib/module/Stepper/index.js +75 -0
- package/lib/module/Stepper/index.js.map +1 -0
- package/lib/module/Stepper/types.js +4 -0
- package/lib/module/Stepper/types.js.map +1 -0
- package/lib/module/TipKit/Popover.js +89 -0
- package/lib/module/TipKit/Popover.js.map +1 -0
- package/lib/module/TipKit/TipPopoverNativeComponent.ts +37 -0
- package/lib/module/TipKit/index.js +28 -0
- package/lib/module/TipKit/index.js.map +1 -0
- package/lib/module/TipKit/types.js +4 -0
- package/lib/module/TipKit/types.js.map +1 -0
- package/lib/module/TipKit/validation.js +24 -0
- package/lib/module/TipKit/validation.js.map +1 -0
- package/lib/module/index.js +4 -0
- package/lib/module/index.js.map +1 -1
- package/lib/module/utils/getNativeTinyui.js.map +1 -1
- package/lib/typescript/src/ConcentricView/ConcentricViewNativeComponent.d.ts +7 -0
- package/lib/typescript/src/ConcentricView/ConcentricViewNativeComponent.d.ts.map +1 -0
- package/lib/typescript/src/ConcentricView/index.d.ts +4 -0
- package/lib/typescript/src/ConcentricView/index.d.ts.map +1 -0
- package/lib/typescript/src/ConcentricView/types.d.ts +6 -0
- package/lib/typescript/src/ConcentricView/types.d.ts.map +1 -0
- package/lib/typescript/src/LiquidGlassText/LiquidGlassTextNativeComponent.d.ts +2 -8
- package/lib/typescript/src/LiquidGlassText/LiquidGlassTextNativeComponent.d.ts.map +1 -1
- package/lib/typescript/src/LiquidGlassText/index.d.ts +1 -1
- package/lib/typescript/src/LiquidGlassText/index.d.ts.map +1 -1
- package/lib/typescript/src/LiquidGlassText/types.d.ts +13 -22
- package/lib/typescript/src/LiquidGlassText/types.d.ts.map +1 -1
- package/lib/typescript/src/Menu/MenuNativeComponent.d.ts +4 -5
- package/lib/typescript/src/Menu/MenuNativeComponent.d.ts.map +1 -1
- package/lib/typescript/src/Menu/index.d.ts +1 -1
- package/lib/typescript/src/Menu/index.d.ts.map +1 -1
- package/lib/typescript/src/Menu/options.d.ts +40 -4
- package/lib/typescript/src/Menu/options.d.ts.map +1 -1
- package/lib/typescript/src/Menu/types.d.ts +16 -4
- package/lib/typescript/src/Menu/types.d.ts.map +1 -1
- package/lib/typescript/src/NativeTinyui.d.ts +2 -0
- package/lib/typescript/src/NativeTinyui.d.ts.map +1 -1
- package/lib/typescript/src/Popover/index.d.ts.map +1 -1
- package/lib/typescript/src/Popover/types.d.ts +2 -0
- package/lib/typescript/src/Popover/types.d.ts.map +1 -1
- package/lib/typescript/src/SFSymbol/SFSymbolNativeComponent.d.ts +11 -0
- package/lib/typescript/src/SFSymbol/SFSymbolNativeComponent.d.ts.map +1 -0
- package/lib/typescript/src/SFSymbol/index.d.ts +4 -0
- package/lib/typescript/src/SFSymbol/index.d.ts.map +1 -0
- package/lib/typescript/src/SFSymbol/options.d.ts +86 -0
- package/lib/typescript/src/SFSymbol/options.d.ts.map +1 -0
- package/lib/typescript/src/SFSymbol/types.d.ts +113 -0
- package/lib/typescript/src/SFSymbol/types.d.ts.map +1 -0
- package/lib/typescript/src/Stepper/StepperNativeComponent.d.ts +21 -0
- package/lib/typescript/src/Stepper/StepperNativeComponent.d.ts.map +1 -0
- package/lib/typescript/src/Stepper/index.d.ts +4 -0
- package/lib/typescript/src/Stepper/index.d.ts.map +1 -0
- package/lib/typescript/src/Stepper/types.d.ts +24 -0
- package/lib/typescript/src/Stepper/types.d.ts.map +1 -0
- package/lib/typescript/src/TipKit/Popover.d.ts +3 -0
- package/lib/typescript/src/TipKit/Popover.d.ts.map +1 -0
- package/lib/typescript/src/TipKit/TipPopoverNativeComponent.d.ts +38 -0
- package/lib/typescript/src/TipKit/TipPopoverNativeComponent.d.ts.map +1 -0
- package/lib/typescript/src/TipKit/index.d.ts +10 -0
- package/lib/typescript/src/TipKit/index.d.ts.map +1 -0
- package/lib/typescript/src/TipKit/types.d.ts +46 -0
- package/lib/typescript/src/TipKit/types.d.ts.map +1 -0
- package/lib/typescript/src/TipKit/validation.d.ts +4 -0
- package/lib/typescript/src/TipKit/validation.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +10 -2
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/utils/getNativeTinyui.d.ts +1 -1
- package/lib/typescript/src/utils/getNativeTinyui.d.ts.map +1 -1
- package/package.json +37 -2
- package/{Tinyui.podspec → react-native-tinyui.podspec} +23 -2
- package/src/ConcentricView/ConcentricViewNativeComponent.ts +14 -0
- package/src/ConcentricView/index.tsx +34 -0
- package/src/ConcentricView/types.ts +6 -0
- package/src/LiquidGlassText/LiquidGlassTextNativeComponent.ts +2 -12
- package/src/LiquidGlassText/index.tsx +73 -39
- package/src/LiquidGlassText/types.ts +24 -50
- package/src/Menu/MenuNativeComponent.ts +7 -5
- package/src/Menu/index.tsx +6 -6
- package/src/Menu/options.ts +4 -18
- package/src/Menu/types.ts +18 -4
- package/src/NativeTinyui.ts +2 -0
- package/src/Popover/index.tsx +8 -3
- package/src/Popover/types.ts +2 -0
- package/src/SFSymbol/SFSymbolNativeComponent.ts +20 -0
- package/src/SFSymbol/index.tsx +89 -0
- package/src/SFSymbol/options.ts +116 -0
- package/src/SFSymbol/types.ts +158 -0
- package/src/Stepper/StepperNativeComponent.ts +29 -0
- package/src/Stepper/index.tsx +91 -0
- package/src/Stepper/types.ts +24 -0
- package/src/TipKit/Popover.tsx +87 -0
- package/src/TipKit/TipPopoverNativeComponent.ts +37 -0
- package/src/TipKit/index.ts +47 -0
- package/src/TipKit/types.ts +57 -0
- package/src/TipKit/validation.ts +39 -0
- package/src/index.tsx +32 -4
- package/src/utils/getNativeTinyui.ts +8 -1
- package/ios/Core/TinyuiHostingHelpers.swift +0 -74
- package/ios/Menu/TinyuiMenuViewSwift.swift +0 -408
- package/ios/Popover/TinyuiPopoverViewSwift.swift +0 -88
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
#if TINYUI_FEATURE_SF_SYMBOL
|
|
2
|
+
|
|
3
|
+
#import "TinyuiSFSymbolImage.h"
|
|
4
|
+
#import <React/RCTConvert.h>
|
|
5
|
+
#include <cmath>
|
|
6
|
+
|
|
7
|
+
static UIImageSymbolWeight symbolWeight(NSString *value)
|
|
8
|
+
{
|
|
9
|
+
NSDictionary *weights = @{
|
|
10
|
+
@"ultraLight": @(UIImageSymbolWeightUltraLight), @"thin": @(UIImageSymbolWeightThin),
|
|
11
|
+
@"light": @(UIImageSymbolWeightLight), @"regular": @(UIImageSymbolWeightRegular),
|
|
12
|
+
@"medium": @(UIImageSymbolWeightMedium), @"semibold": @(UIImageSymbolWeightSemibold),
|
|
13
|
+
@"bold": @(UIImageSymbolWeightBold), @"heavy": @(UIImageSymbolWeightHeavy),
|
|
14
|
+
@"black": @(UIImageSymbolWeightBlack),
|
|
15
|
+
};
|
|
16
|
+
return (UIImageSymbolWeight)[weights[value ?: @""] integerValue];
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
static UIImageSymbolScale symbolScale(NSString *value)
|
|
20
|
+
{
|
|
21
|
+
if ([value isEqual:@"small"]) return UIImageSymbolScaleSmall;
|
|
22
|
+
if ([value isEqual:@"medium"]) return UIImageSymbolScaleMedium;
|
|
23
|
+
if ([value isEqual:@"large"]) return UIImageSymbolScaleLarge;
|
|
24
|
+
if ([value isEqual:@"unspecified"]) return UIImageSymbolScaleUnspecified;
|
|
25
|
+
return UIImageSymbolScaleDefault;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
static UIFontTextStyle textStyle(NSString *value)
|
|
29
|
+
{
|
|
30
|
+
return @{
|
|
31
|
+
@"extraLargeTitle": UIFontTextStyleExtraLargeTitle,
|
|
32
|
+
@"extraLargeTitle2": UIFontTextStyleExtraLargeTitle2,
|
|
33
|
+
@"largeTitle": UIFontTextStyleLargeTitle, @"title1": UIFontTextStyleTitle1,
|
|
34
|
+
@"title2": UIFontTextStyleTitle2, @"title3": UIFontTextStyleTitle3,
|
|
35
|
+
@"headline": UIFontTextStyleHeadline, @"subheadline": UIFontTextStyleSubheadline,
|
|
36
|
+
@"body": UIFontTextStyleBody, @"callout": UIFontTextStyleCallout,
|
|
37
|
+
@"footnote": UIFontTextStyleFootnote, @"caption1": UIFontTextStyleCaption1,
|
|
38
|
+
@"caption2": UIFontTextStyleCaption2,
|
|
39
|
+
}[value ?: @""];
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
UIImage *TinyuiSFSymbolImage(NSDictionary *configuration, BOOL includeColors)
|
|
43
|
+
{
|
|
44
|
+
if (![configuration isKindOfClass:NSDictionary.class]) return nil;
|
|
45
|
+
NSString *name = configuration[@"name"];
|
|
46
|
+
if (![name isKindOfClass:NSString.class] || name.length == 0) return nil;
|
|
47
|
+
|
|
48
|
+
CGFloat size = [configuration[@"size"] doubleValue];
|
|
49
|
+
if (!std::isfinite(size) || size <= 0) size = 17;
|
|
50
|
+
UIImageSymbolWeight weight = symbolWeight(configuration[@"weight"]);
|
|
51
|
+
UIImageSymbolScale scale = symbolScale(configuration[@"scale"]);
|
|
52
|
+
UIFontTextStyle style = textStyle(configuration[@"textStyle"]);
|
|
53
|
+
if (style != nil) {
|
|
54
|
+
// RN supplies the scale explicitly; use unscaled metrics here to avoid
|
|
55
|
+
// applying Dynamic Type twice and to keep Yoga and UIKit in agreement.
|
|
56
|
+
UITraitCollection *traits = [UITraitCollection
|
|
57
|
+
traitCollectionWithPreferredContentSizeCategory:UIContentSizeCategoryLarge];
|
|
58
|
+
UIFont *font = [UIFont preferredFontForTextStyle:style compatibleWithTraitCollection:traits];
|
|
59
|
+
size = font.pointSize;
|
|
60
|
+
if (weight == UIImageSymbolWeightUnspecified) {
|
|
61
|
+
NSNumber *fontWeight = font.fontDescriptor.fontAttributes[UIFontDescriptorTraitsAttribute][UIFontWeightTrait];
|
|
62
|
+
weight = UIImageSymbolWeightForFontWeight(fontWeight.doubleValue);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
double fontScale = [configuration[@"fontScale"] doubleValue];
|
|
66
|
+
if (std::isfinite(fontScale) && fontScale > 0) size *= fontScale;
|
|
67
|
+
|
|
68
|
+
UIImageSymbolConfiguration *symbol = [UIImageSymbolConfiguration
|
|
69
|
+
configurationWithPointSize:size weight:weight scale:scale];
|
|
70
|
+
NSString *family = configuration[@"fontFamily"];
|
|
71
|
+
if ([family isKindOfClass:NSString.class] && family.length > 0) {
|
|
72
|
+
UIFont *font = [UIFont fontWithName:family size:size];
|
|
73
|
+
if (font != nil) {
|
|
74
|
+
symbol = [UIImageSymbolConfiguration configurationWithFont:font scale:scale];
|
|
75
|
+
if (weight != UIImageSymbolWeightUnspecified) {
|
|
76
|
+
symbol = [symbol configurationByApplyingConfiguration:
|
|
77
|
+
[UIImageSymbolConfiguration configurationWithWeight:weight]];
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (includeColors) {
|
|
83
|
+
NSString *mode = configuration[@"renderingMode"];
|
|
84
|
+
UIColor *color = [RCTConvert UIColor:configuration[@"color"]] ?: UIColor.labelColor;
|
|
85
|
+
UIImageSymbolConfiguration *colors = nil;
|
|
86
|
+
if ([mode isEqual:@"monochrome"]) {
|
|
87
|
+
colors = UIImageSymbolConfiguration.configurationPreferringMonochrome;
|
|
88
|
+
} else if ([mode isEqual:@"multicolor"]) {
|
|
89
|
+
colors = UIImageSymbolConfiguration.configurationPreferringMulticolor;
|
|
90
|
+
} else if ([mode isEqual:@"hierarchical"]) {
|
|
91
|
+
colors = [UIImageSymbolConfiguration configurationWithHierarchicalColor:color];
|
|
92
|
+
} else if ([mode isEqual:@"palette"]) {
|
|
93
|
+
NSMutableArray<UIColor *> *palette = [NSMutableArray array];
|
|
94
|
+
id values = configuration[@"paletteColors"];
|
|
95
|
+
if ([values isKindOfClass:NSArray.class]) {
|
|
96
|
+
for (id value in values) {
|
|
97
|
+
UIColor *paletteColor = [RCTConvert UIColor:value];
|
|
98
|
+
if (paletteColor != nil && palette.count < 3) [palette addObject:paletteColor];
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
if (palette.count == 0) [palette addObject:color];
|
|
102
|
+
colors = [UIImageSymbolConfiguration configurationWithPaletteColors:palette];
|
|
103
|
+
}
|
|
104
|
+
if (colors != nil) symbol = [symbol configurationByApplyingConfiguration:colors];
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (@available(iOS 26.0, *)) {
|
|
108
|
+
NSString *variableMode = configuration[@"variableValueMode"];
|
|
109
|
+
UIImageSymbolVariableValueMode variable = UIImageSymbolVariableValueModeAutomatic;
|
|
110
|
+
if ([variableMode isEqual:@"color"]) variable = UIImageSymbolVariableValueModeColor;
|
|
111
|
+
if ([variableMode isEqual:@"draw"]) variable = UIImageSymbolVariableValueModeDraw;
|
|
112
|
+
symbol = [symbol configurationByApplyingConfiguration:
|
|
113
|
+
[UIImageSymbolConfiguration configurationWithVariableValueMode:variable]];
|
|
114
|
+
|
|
115
|
+
NSString *colorMode = configuration[@"colorRenderingMode"];
|
|
116
|
+
UIImageSymbolColorRenderingMode rendering = UIImageSymbolColorRenderingModeAutomatic;
|
|
117
|
+
if ([colorMode isEqual:@"flat"]) rendering = UIImageSymbolColorRenderingModeFlat;
|
|
118
|
+
if ([colorMode isEqual:@"gradient"]) rendering = UIImageSymbolColorRenderingModeGradient;
|
|
119
|
+
symbol = [symbol configurationByApplyingConfiguration:
|
|
120
|
+
[UIImageSymbolConfiguration configurationWithColorRenderingMode:rendering]];
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
BOOL asset = [configuration[@"source"] isEqual:@"asset"];
|
|
124
|
+
id variableValue = configuration[@"variableValue"];
|
|
125
|
+
UIImage *image;
|
|
126
|
+
if ([variableValue isKindOfClass:NSNumber.class]) {
|
|
127
|
+
double value = [variableValue doubleValue];
|
|
128
|
+
value = std::isfinite(value) ? fmin(1, fmax(0, value)) : 0;
|
|
129
|
+
image = asset
|
|
130
|
+
? [UIImage imageNamed:name inBundle:NSBundle.mainBundle variableValue:value withConfiguration:symbol]
|
|
131
|
+
: [UIImage systemImageNamed:name variableValue:value withConfiguration:symbol];
|
|
132
|
+
} else {
|
|
133
|
+
image = asset
|
|
134
|
+
? [UIImage imageNamed:name inBundle:NSBundle.mainBundle withConfiguration:symbol]
|
|
135
|
+
: [UIImage systemImageNamed:name withConfiguration:symbol];
|
|
136
|
+
}
|
|
137
|
+
// Symbol content transitions are undefined for ordinary bitmap assets.
|
|
138
|
+
return image.isSymbolImage ? image : nil;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
#endif
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
#include <react/renderer/components/TinyuiSpec/ShadowNodes.h>
|
|
4
|
+
#include <react/renderer/core/ConcreteComponentDescriptor.h>
|
|
5
|
+
|
|
6
|
+
namespace facebook::react {
|
|
7
|
+
|
|
8
|
+
class TinyuiSFSymbolShadowNode final
|
|
9
|
+
: public ConcreteViewShadowNode<
|
|
10
|
+
TinyuiSFSymbolViewComponentName,
|
|
11
|
+
TinyuiSFSymbolViewProps,
|
|
12
|
+
TinyuiSFSymbolViewEventEmitter> {
|
|
13
|
+
public:
|
|
14
|
+
using ConcreteViewShadowNode::ConcreteViewShadowNode;
|
|
15
|
+
|
|
16
|
+
static ShadowNodeTraits BaseTraits()
|
|
17
|
+
{
|
|
18
|
+
auto traits = ConcreteViewShadowNode::BaseTraits();
|
|
19
|
+
traits.set(ShadowNodeTraits::Trait::LeafYogaNode);
|
|
20
|
+
traits.set(ShadowNodeTraits::Trait::MeasurableYogaNode);
|
|
21
|
+
return traits;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
Size measureContent(
|
|
25
|
+
const LayoutContext &layoutContext,
|
|
26
|
+
const LayoutConstraints &layoutConstraints) const override;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
using TinyuiSFSymbolComponentDescriptor = ConcreteComponentDescriptor<TinyuiSFSymbolShadowNode>;
|
|
30
|
+
|
|
31
|
+
} // namespace facebook::react
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
#if TINYUI_FEATURE_SF_SYMBOL
|
|
2
|
+
|
|
3
|
+
#import "TinyuiSFSymbolShadowNode.h"
|
|
4
|
+
#import "TinyuiSFSymbolImage.h"
|
|
5
|
+
#import <react/utils/FollyConvert.h>
|
|
6
|
+
#include <react/renderer/core/LayoutConstraints.h>
|
|
7
|
+
#include <react/renderer/core/LayoutContext.h>
|
|
8
|
+
#include <cmath>
|
|
9
|
+
|
|
10
|
+
namespace facebook::react {
|
|
11
|
+
|
|
12
|
+
Size TinyuiSFSymbolShadowNode::measureContent(
|
|
13
|
+
const LayoutContext &layoutContext,
|
|
14
|
+
const LayoutConstraints &layoutConstraints) const
|
|
15
|
+
{
|
|
16
|
+
@autoreleasepool {
|
|
17
|
+
// UIImage and UIFont creation are safe off the main thread. No UIView or
|
|
18
|
+
// main-queue sync is needed during concurrent Fabric layout.
|
|
19
|
+
UIImage *image = TinyuiSFSymbolImage(convertFollyDynamicToId(getConcreteProps().configuration), NO);
|
|
20
|
+
CGSize size = image == nil ? CGSizeZero : image.size;
|
|
21
|
+
auto scale = layoutContext.pointScaleFactor > 0 ? layoutContext.pointScaleFactor : 1;
|
|
22
|
+
return layoutConstraints.clamp({
|
|
23
|
+
static_cast<Float>(std::ceil(size.width * scale) / scale),
|
|
24
|
+
static_cast<Float>(std::ceil(size.height * scale) / scale)});
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
} // namespace facebook::react
|
|
29
|
+
|
|
30
|
+
#endif
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
#if TINYUI_FEATURE_SF_SYMBOL
|
|
2
|
+
|
|
3
|
+
#import "TinyuiSFSymbolView.h"
|
|
4
|
+
#import "TinyuiSFSymbolEffects.h"
|
|
5
|
+
#import "TinyuiSFSymbolImage.h"
|
|
6
|
+
#import "TinyuiSFSymbolShadowNode.h"
|
|
7
|
+
#import <React/RCTConvert.h>
|
|
8
|
+
#import <react/utils/FollyConvert.h>
|
|
9
|
+
#import <react/renderer/components/TinyuiSpec/Props.h>
|
|
10
|
+
#import <react/renderer/components/TinyuiSpec/RCTComponentViewHelpers.h>
|
|
11
|
+
|
|
12
|
+
using namespace facebook::react;
|
|
13
|
+
|
|
14
|
+
@interface TinyuiSFSymbolView () <RCTTinyuiSFSymbolViewViewProtocol>
|
|
15
|
+
@end
|
|
16
|
+
|
|
17
|
+
@implementation TinyuiSFSymbolView {
|
|
18
|
+
UIImageView *_imageView;
|
|
19
|
+
NSDictionary *_configuration;
|
|
20
|
+
NSArray<NSDictionary *> *_effects;
|
|
21
|
+
NSMutableDictionary<NSString *, NSDictionary *> *_appliedEffects;
|
|
22
|
+
BOOL _respectReduceMotion;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
+ (ComponentDescriptorProvider)componentDescriptorProvider
|
|
26
|
+
{
|
|
27
|
+
return concreteComponentDescriptorProvider<TinyuiSFSymbolComponentDescriptor>();
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
- (instancetype)initWithFrame:(CGRect)frame
|
|
31
|
+
{
|
|
32
|
+
if (self = [super initWithFrame:frame]) {
|
|
33
|
+
_props = std::make_shared<const TinyuiSFSymbolViewProps>();
|
|
34
|
+
[self createImageView];
|
|
35
|
+
[NSNotificationCenter.defaultCenter addObserver:self selector:@selector(reduceMotionChanged:)
|
|
36
|
+
name:UIAccessibilityReduceMotionStatusDidChangeNotification object:nil];
|
|
37
|
+
}
|
|
38
|
+
return self;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
- (void)createImageView
|
|
42
|
+
{
|
|
43
|
+
_imageView = [UIImageView new];
|
|
44
|
+
_imageView.contentMode = UIViewContentModeCenter;
|
|
45
|
+
_imageView.isAccessibilityElement = NO;
|
|
46
|
+
self.contentView = _imageView;
|
|
47
|
+
_configuration = nil;
|
|
48
|
+
_effects = @[];
|
|
49
|
+
_appliedEffects = [NSMutableDictionary dictionary];
|
|
50
|
+
_respectReduceMotion = YES;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
- (BOOL)reduceMotion
|
|
54
|
+
{
|
|
55
|
+
return _respectReduceMotion && UIAccessibilityIsReduceMotionEnabled();
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
- (void)updateProps:(Props::Shared const &)props oldProps:(Props::Shared const &)oldProps
|
|
59
|
+
{
|
|
60
|
+
const auto &next = *std::static_pointer_cast<const TinyuiSFSymbolViewProps>(props);
|
|
61
|
+
BOOL changedMotion = _respectReduceMotion != next.respectReduceMotion;
|
|
62
|
+
_respectReduceMotion = next.respectReduceMotion;
|
|
63
|
+
if (changedMotion) [self clearEffects];
|
|
64
|
+
|
|
65
|
+
NSDictionary *configuration = convertFollyDynamicToId(next.configuration);
|
|
66
|
+
if (![configuration isKindOfClass:NSDictionary.class]) configuration = @{};
|
|
67
|
+
if (![_configuration isEqual:configuration]) {
|
|
68
|
+
UIImage *image = TinyuiSFSymbolImage(configuration, YES);
|
|
69
|
+
_imageView.tintColor = [RCTConvert UIColor:configuration[@"color"]] ?: UIColor.labelColor;
|
|
70
|
+
NSDictionary *transitionConfig = convertFollyDynamicToId(next.contentTransition);
|
|
71
|
+
if (![transitionConfig isKindOfClass:NSDictionary.class]) transitionConfig = @{};
|
|
72
|
+
NSSymbolContentTransition *transition = TinyuiSFSymbolTransition(transitionConfig);
|
|
73
|
+
if (image != nil && _imageView.image != nil && self.window != nil && transition != nil && !self.reduceMotion) {
|
|
74
|
+
[_imageView setSymbolImage:image withContentTransition:transition
|
|
75
|
+
options:TinyuiSFSymbolOptions(transitionConfig)];
|
|
76
|
+
} else {
|
|
77
|
+
_imageView.image = image;
|
|
78
|
+
}
|
|
79
|
+
_configuration = configuration;
|
|
80
|
+
if (image == nil) [self clearEffects];
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (next.resizeMode == "contain") _imageView.contentMode = UIViewContentModeScaleAspectFit;
|
|
84
|
+
else if (next.resizeMode == "cover") _imageView.contentMode = UIViewContentModeScaleAspectFill;
|
|
85
|
+
else if (next.resizeMode == "stretch") _imageView.contentMode = UIViewContentModeScaleToFill;
|
|
86
|
+
else _imageView.contentMode = UIViewContentModeCenter;
|
|
87
|
+
|
|
88
|
+
id effects = convertFollyDynamicToId(next.effects);
|
|
89
|
+
_effects = [effects isKindOfClass:NSArray.class] ? effects : @[];
|
|
90
|
+
[super updateProps:props oldProps:oldProps];
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
- (void)finalizeUpdates:(RNComponentViewUpdateMask)updateMask
|
|
94
|
+
{
|
|
95
|
+
[super finalizeUpdates:updateMask];
|
|
96
|
+
[self applyEffects];
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
- (void)clearEffects
|
|
100
|
+
{
|
|
101
|
+
[_imageView removeAllSymbolEffectsWithOptions:NSSymbolEffectOptions.options animated:NO];
|
|
102
|
+
[_appliedEffects removeAllObjects];
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
- (void)applyEffects
|
|
106
|
+
{
|
|
107
|
+
if (self.window == nil || _imageView.image == nil) return;
|
|
108
|
+
NSMutableDictionary<NSString *, NSDictionary *> *requested = [NSMutableDictionary dictionary];
|
|
109
|
+
for (NSDictionary *configuration in _effects) {
|
|
110
|
+
if (![configuration isKindOfClass:NSDictionary.class]) continue;
|
|
111
|
+
NSString *type = configuration[@"type"];
|
|
112
|
+
if (![type isKindOfClass:NSString.class]) continue;
|
|
113
|
+
requested[type] = configuration;
|
|
114
|
+
}
|
|
115
|
+
for (NSString *type in _appliedEffects.allKeys) {
|
|
116
|
+
NSDictionary *old = _appliedEffects[type];
|
|
117
|
+
NSDictionary *next = requested[type];
|
|
118
|
+
if (![old isEqual:next] || [next[@"active"] isEqual:@NO]) {
|
|
119
|
+
NSSymbolEffect *effect = TinyuiSFSymbolEffect(old);
|
|
120
|
+
// Reset immediately when replaying/reconfiguring, so an animated removal
|
|
121
|
+
// cannot race the replacement effect. Animate only actual deactivation.
|
|
122
|
+
BOOL reapplying = next != nil && ![next[@"active"] isEqual:@NO];
|
|
123
|
+
BOOL animated = !reapplying && !self.reduceMotion && ![(next ?: old)[@"animated"] isEqual:@NO];
|
|
124
|
+
if (effect != nil) {
|
|
125
|
+
[_imageView removeSymbolEffectOfType:effect
|
|
126
|
+
options:TinyuiSFSymbolOptions((next ?: old)[@"options"]) animated:animated];
|
|
127
|
+
}
|
|
128
|
+
[_appliedEffects removeObjectForKey:type];
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
for (NSString *type in requested) {
|
|
132
|
+
NSDictionary *configuration = requested[type];
|
|
133
|
+
if ([configuration[@"active"] isEqual:@NO] || [_appliedEffects[type] isEqual:configuration]) continue;
|
|
134
|
+
BOOL stateful = [@[@"scale", @"appear", @"disappear", @"drawOn", @"drawOff"] containsObject:type];
|
|
135
|
+
// Keep visibility/scale state under Reduce Motion, without continuous or
|
|
136
|
+
// discrete animation. Clearing those effects must still restore the state.
|
|
137
|
+
if (self.reduceMotion && !stateful) continue;
|
|
138
|
+
NSSymbolEffect *effect = TinyuiSFSymbolEffect(configuration);
|
|
139
|
+
if (effect == nil) continue;
|
|
140
|
+
[_imageView addSymbolEffect:effect options:TinyuiSFSymbolOptions(configuration[@"options"])
|
|
141
|
+
animated:!self.reduceMotion && ![configuration[@"animated"] isEqual:@NO]];
|
|
142
|
+
_appliedEffects[type] = configuration;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
- (void)didMoveToWindow
|
|
147
|
+
{
|
|
148
|
+
[super didMoveToWindow];
|
|
149
|
+
if (self.window == nil) [self clearEffects];
|
|
150
|
+
else [self applyEffects];
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
- (void)reduceMotionChanged:(NSNotification *)notification
|
|
154
|
+
{
|
|
155
|
+
[self clearEffects];
|
|
156
|
+
[self applyEffects];
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
- (void)prepareForRecycle
|
|
160
|
+
{
|
|
161
|
+
[self clearEffects];
|
|
162
|
+
[super prepareForRecycle];
|
|
163
|
+
[self createImageView];
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
- (void)dealloc
|
|
167
|
+
{
|
|
168
|
+
[NSNotificationCenter.defaultCenter removeObserver:self];
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
@end
|
|
172
|
+
|
|
173
|
+
#endif
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
#include <react/renderer/components/TinyuiSpec/ShadowNodes.h>
|
|
4
|
+
#include <react/renderer/core/ConcreteComponentDescriptor.h>
|
|
5
|
+
#include <react/renderer/core/LayoutConstraints.h>
|
|
6
|
+
|
|
7
|
+
namespace facebook::react {
|
|
8
|
+
|
|
9
|
+
class TinyuiStepperShadowNode final
|
|
10
|
+
: public ConcreteViewShadowNode<
|
|
11
|
+
TinyuiStepperViewComponentName,
|
|
12
|
+
TinyuiStepperViewProps,
|
|
13
|
+
TinyuiStepperViewEventEmitter> {
|
|
14
|
+
public:
|
|
15
|
+
using ConcreteViewShadowNode::ConcreteViewShadowNode;
|
|
16
|
+
|
|
17
|
+
static ShadowNodeTraits BaseTraits()
|
|
18
|
+
{
|
|
19
|
+
auto traits = ConcreteViewShadowNode::BaseTraits();
|
|
20
|
+
traits.set(ShadowNodeTraits::Trait::LeafYogaNode);
|
|
21
|
+
traits.set(ShadowNodeTraits::Trait::MeasurableYogaNode);
|
|
22
|
+
return traits;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
Size measureContent(
|
|
26
|
+
const LayoutContext &layoutContext,
|
|
27
|
+
const LayoutConstraints &layoutConstraints) const override;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
using TinyuiStepperComponentDescriptor = ConcreteComponentDescriptor<TinyuiStepperShadowNode>;
|
|
31
|
+
|
|
32
|
+
} // namespace facebook::react
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
#if TINYUI_FEATURE_STEPPER
|
|
2
|
+
|
|
3
|
+
#import "TinyuiStepperShadowNode.h"
|
|
4
|
+
|
|
5
|
+
#import <React/RCTUtils.h>
|
|
6
|
+
#import <UIKit/UIKit.h>
|
|
7
|
+
|
|
8
|
+
namespace facebook::react {
|
|
9
|
+
|
|
10
|
+
Size TinyuiStepperShadowNode::measureContent(
|
|
11
|
+
const LayoutContext &layoutContext,
|
|
12
|
+
const LayoutConstraints &layoutConstraints) const
|
|
13
|
+
{
|
|
14
|
+
// Match React Native's RCTSwitchSize: cache the native size and create the
|
|
15
|
+
// UIKit control on the main queue, even when Yoga measures on a worker.
|
|
16
|
+
static CGSize size;
|
|
17
|
+
static dispatch_once_t onceToken;
|
|
18
|
+
dispatch_once(&onceToken, ^{
|
|
19
|
+
RCTUnsafeExecuteOnMainQueueSync(^{
|
|
20
|
+
size = [UIStepper new].intrinsicContentSize;
|
|
21
|
+
});
|
|
22
|
+
});
|
|
23
|
+
return layoutConstraints.clamp({size.width, size.height});
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
} // namespace facebook::react
|
|
27
|
+
|
|
28
|
+
#endif
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
#if TINYUI_FEATURE_STEPPER
|
|
2
|
+
|
|
3
|
+
#import "TinyuiStepperView.h"
|
|
4
|
+
#import "TinyuiStepperShadowNode.h"
|
|
5
|
+
|
|
6
|
+
#import <react/renderer/components/TinyuiSpec/EventEmitters.h>
|
|
7
|
+
#import <react/renderer/components/TinyuiSpec/Props.h>
|
|
8
|
+
#import <react/renderer/components/TinyuiSpec/RCTComponentViewHelpers.h>
|
|
9
|
+
|
|
10
|
+
#include <algorithm>
|
|
11
|
+
#include <cmath>
|
|
12
|
+
|
|
13
|
+
using namespace facebook::react;
|
|
14
|
+
|
|
15
|
+
@interface TinyuiStepperView () <RCTTinyuiStepperViewViewProtocol>
|
|
16
|
+
@end
|
|
17
|
+
|
|
18
|
+
@implementation TinyuiStepperView {
|
|
19
|
+
UIStepper *_stepper;
|
|
20
|
+
int _nativeEventCount;
|
|
21
|
+
BOOL _hasInitialValue;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
+ (ComponentDescriptorProvider)componentDescriptorProvider
|
|
25
|
+
{
|
|
26
|
+
return concreteComponentDescriptorProvider<TinyuiStepperComponentDescriptor>();
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
- (instancetype)initWithFrame:(CGRect)frame
|
|
30
|
+
{
|
|
31
|
+
if (self = [super initWithFrame:frame]) {
|
|
32
|
+
static const auto defaultProps = std::make_shared<const TinyuiStepperViewProps>();
|
|
33
|
+
_props = defaultProps;
|
|
34
|
+
[self createStepper];
|
|
35
|
+
}
|
|
36
|
+
return self;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
- (void)createStepper
|
|
40
|
+
{
|
|
41
|
+
_stepper = [[UIStepper alloc] initWithFrame:CGRectZero];
|
|
42
|
+
[_stepper addTarget:self action:@selector(onChange:) forControlEvents:UIControlEventValueChanged];
|
|
43
|
+
self.contentView = _stepper;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
- (void)updateProps:(Props::Shared const &)props oldProps:(Props::Shared const &)oldProps
|
|
47
|
+
{
|
|
48
|
+
const auto &newProps = *std::static_pointer_cast<TinyuiStepperViewProps const>(props);
|
|
49
|
+
|
|
50
|
+
// The public wrapper validates these; also guard the native boundary against
|
|
51
|
+
// invalid numbers, since UIStepper raises for nonpositive increments.
|
|
52
|
+
double minimum = std::isfinite(newProps.minimumValue) ? newProps.minimumValue : 0;
|
|
53
|
+
double maximum = std::isfinite(newProps.maximumValue) ? newProps.maximumValue : 100;
|
|
54
|
+
maximum = std::max(minimum, maximum);
|
|
55
|
+
double previousValue = _stepper.value;
|
|
56
|
+
|
|
57
|
+
// Expand the old range before shrinking it. Setting disjoint bounds in the
|
|
58
|
+
// wrong order lets UIKit change the other bound and prematurely clamp value.
|
|
59
|
+
_stepper.minimumValue = std::min(_stepper.minimumValue, minimum);
|
|
60
|
+
_stepper.maximumValue = std::max(_stepper.maximumValue, maximum);
|
|
61
|
+
_stepper.minimumValue = minimum;
|
|
62
|
+
_stepper.maximumValue = maximum;
|
|
63
|
+
_stepper.stepValue = std::isfinite(newProps.stepValue) && newProps.stepValue > 0
|
|
64
|
+
? newProps.stepValue : 1;
|
|
65
|
+
_stepper.continuous = newProps.isContinuous;
|
|
66
|
+
_stepper.autorepeat = newProps.autorepeat;
|
|
67
|
+
_stepper.wraps = newProps.wraps;
|
|
68
|
+
_stepper.enabled = !newProps.disabled;
|
|
69
|
+
|
|
70
|
+
// Uncontrolled values stay in UIKit. Controlled values are applied only once
|
|
71
|
+
// JS has acknowledged the newest native event, avoiding autorepeat rollback.
|
|
72
|
+
double value = previousValue;
|
|
73
|
+
if (!_hasInitialValue ||
|
|
74
|
+
(newProps.controlled && newProps.mostRecentEventCount == _nativeEventCount)) {
|
|
75
|
+
value = std::isfinite(newProps.value) ? newProps.value : minimum;
|
|
76
|
+
}
|
|
77
|
+
_stepper.value = std::clamp(value, minimum, maximum);
|
|
78
|
+
_hasInitialValue = YES;
|
|
79
|
+
|
|
80
|
+
[super updateProps:props oldProps:oldProps];
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
- (void)layoutSubviews
|
|
84
|
+
{
|
|
85
|
+
[super layoutSubviews];
|
|
86
|
+
// UIKit controls keep their natural size; a larger React frame centers them.
|
|
87
|
+
CGSize size = [_stepper sizeThatFits:self.bounds.size];
|
|
88
|
+
_stepper.frame = CGRectMake(
|
|
89
|
+
(CGRectGetWidth(self.bounds) - size.width) / 2,
|
|
90
|
+
(CGRectGetHeight(self.bounds) - size.height) / 2,
|
|
91
|
+
size.width,
|
|
92
|
+
size.height);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
- (void)onChange:(UIStepper *)sender
|
|
96
|
+
{
|
|
97
|
+
if (_eventEmitter == nullptr) {
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
_nativeEventCount += 1;
|
|
101
|
+
std::static_pointer_cast<const TinyuiStepperViewEventEmitter>(_eventEmitter)
|
|
102
|
+
->onChange(TinyuiStepperViewEventEmitter::OnChange{
|
|
103
|
+
.value = sender.value,
|
|
104
|
+
.eventCount = _nativeEventCount});
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Let UIStepper expose its native accessibility controls and actions.
|
|
108
|
+
- (BOOL)isAccessibilityElement
|
|
109
|
+
{
|
|
110
|
+
return NO;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
- (NSObject *)accessibilityElement
|
|
114
|
+
{
|
|
115
|
+
return _stepper;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
- (void)prepareForRecycle
|
|
119
|
+
{
|
|
120
|
+
[super prepareForRecycle];
|
|
121
|
+
_nativeEventCount = 0;
|
|
122
|
+
_hasInitialValue = NO;
|
|
123
|
+
[self createStepper];
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
@end
|
|
127
|
+
|
|
128
|
+
#endif
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import SwiftUI
|
|
2
|
+
import TipKit
|
|
3
|
+
|
|
4
|
+
struct TinyuiTip: Tip, Sendable {
|
|
5
|
+
struct ActionContent: Equatable, Sendable {
|
|
6
|
+
let id: String
|
|
7
|
+
let title: String
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
let id: String
|
|
11
|
+
var titleString = ""
|
|
12
|
+
var messageString = ""
|
|
13
|
+
var symbolName = ""
|
|
14
|
+
var actionContents: [ActionContent] = []
|
|
15
|
+
var maximumDisplayCount = 0
|
|
16
|
+
var ignoresFrequency = false
|
|
17
|
+
|
|
18
|
+
var title: Text { Text(verbatim: titleString) }
|
|
19
|
+
var message: Text? { messageString.isEmpty ? nil : Text(verbatim: messageString) }
|
|
20
|
+
var image: Image? { symbolName.isEmpty ? nil : Image(systemName: symbolName) }
|
|
21
|
+
var actions: [Action] {
|
|
22
|
+
return actionContents.map { Action(id: $0.id, title: $0.title) }
|
|
23
|
+
}
|
|
24
|
+
var options: [any TipOption] {
|
|
25
|
+
var result: [any TipOption] = [Tips.IgnoresDisplayFrequency(ignoresFrequency)]
|
|
26
|
+
if maximumDisplayCount > 0 {
|
|
27
|
+
result.append(Tips.MaxDisplayCount(maximumDisplayCount))
|
|
28
|
+
}
|
|
29
|
+
return result
|
|
30
|
+
}
|
|
31
|
+
}
|