native_adaptive_toolbox 0.1.0 copy "native_adaptive_toolbox: ^0.1.0" to clipboard
native_adaptive_toolbox: ^0.1.0 copied to clipboard

Native text-selection menus and custom-painted handles, foundation-only.

example/lib/main.dart

import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart';
import 'package:native_adaptive_toolbox/native_adaptive_toolbox.dart';

void main() => runApp(const ToolboxDemoApp());

/// Demonstrates the native context menu and the foundation-only handles.
class ToolboxDemoApp extends StatelessWidget {
  const ToolboxDemoApp({super.key});

  @override
  Widget build(BuildContext context) {
    return WidgetsApp(
      color: const Color(0xFFF2F3F5),
      title: 'Native Adaptive Toolbox Demo',
      builder: (context, _) => const Directionality(
        textDirection: TextDirection.ltr,
        child: Center(child: DemoSurface()),
      ),
    );
  }
}

class DemoSurface extends StatefulWidget {
  const DemoSurface({super.key});

  @override
  State<DemoSurface> createState() => _DemoSurfaceState();
}

class _DemoSurfaceState extends State<DemoSurface> {
  static const words = [
    'Alpha',
    'bravo',
    'charlie',
    'delta',
    'echo',
    'foxtrot',
  ];

  final GlobalKey _textKey = GlobalKey();
  final NativeToolboxSelectionControls _controls =
      NativeToolboxSelectionControls(platform: defaultTargetPlatform);
  String? _selectedWord;
  String _status = 'Long-press or right-click a word.';

  @override
  void initState() {
    super.initState();
    NativeToolboxMenu.onAction.listen(_handleAction);
    NativeToolboxMenu.onDismiss.listen((_) {
      if (mounted) setState(() => _status = 'Menu dismissed.');
    });
  }

  void _handleAction(int index) {
    const actions = ['Copy', 'Cut', 'Paste', 'Select all'];
    final word = _selectedWord;
    if (index < 0 || index >= actions.length) return;
    final label = actions[index];
    if (word != null && (index == 0 || index == 1)) {
      Clipboard.setData(ClipboardData(text: word));
    }
    if (mounted) {
      setState(() => _status = '$label tapped (selected word: $word).');
    }
  }

  Future<void> _showMenu(Offset globalPosition) async {
    final box = _textKey.currentContext!.findRenderObject()! as RenderBox;
    final (point, topRight) = NativeToolboxPositioner.resolvePointAnchor(
      globalPosition,
      Rect.fromLTWH(0, 0, box.size.width, box.size.height),
      rtl: false,
    );
    final shown = await NativeToolboxMenu.showMenu(
      anchorRect: Rect.fromPoints(point, point),
      actions: const [
        NativeToolboxAction(id: 'copy', label: 'Copy'),
        NativeToolboxAction(id: 'cut', label: 'Cut'),
        NativeToolboxAction(id: 'paste', label: 'Paste'),
        NativeToolboxAction(id: 'selectAll', label: 'Select all'),
      ],
      selectionMode: _selectedWord != null,
    );
    if (!shown && mounted) {
      debugPrint('PROBE showMenu returned false');
      setState(() => _status = 'No platform implementation on this device.');
    }
  }

  @override
  Widget build(BuildContext context) {
    final selected = _selectedWord;
    return Padding(
      padding: const EdgeInsets.all(24),
      child: Column(
        mainAxisSize: MainAxisSize.min,
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          const Text(
            'native_adaptive_toolbox demo',
            style: TextStyle(fontSize: 20, fontWeight: FontWeight.w600),
          ),
          const SizedBox(height: 16),
          Wrap(
            key: _textKey,
            spacing: 8,
            runSpacing: 8,
            children: [
              for (final word in words)
                _WordChip(
                  word: word,
                  selected: word == selected,
                  controls: _controls,
                  onLongPress: (position) => _showMenu(position),
                  // On web the browser owns menus for selectable text; the
                  // chips are not a text surface, so there is nothing native
                  // to show here (the live_markdown_editor integration
                  // demonstrates the browser menu on real selections).
                  onSecondaryTap: kIsWeb
                      ? (_) => setState(
                          () => _status =
                              'Web: the browser owns menus for text selection.',
                        )
                      : (position) => _showMenu(position),
                  onSelect: () => setState(() => _selectedWord = word),
                ),
            ],
          ),
          const SizedBox(height: 16),
          Text(_status, style: const TextStyle(fontSize: 14)),
        ],
      ),
    );
  }
}

class _WordChip extends StatelessWidget {
  const _WordChip({
    required this.word,
    required this.selected,
    required this.controls,
    required this.onLongPress,
    required this.onSecondaryTap,
    required this.onSelect,
  });

  final String word;
  final bool selected;
  final NativeToolboxSelectionControls controls;
  final ValueChanged<Offset> onLongPress;
  final ValueChanged<Offset>? onSecondaryTap;
  final VoidCallback onSelect;

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      key: ValueKey('word-$word'),
      behavior: HitTestBehavior.opaque,
      onTap: onSelect,
      onLongPress: () {
        onSelect();
        final box = context.findRenderObject()! as RenderBox;
        onLongPress(box.localToGlobal(box.size.center(Offset.zero)));
      },
      onSecondaryTapDown: onSecondaryTap == null
          ? null
          : (details) {
              onSelect();
              onSecondaryTap!(details.globalPosition);
            },
      child: Container(
        padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
        decoration: BoxDecoration(
          color: selected ? const Color(0x1F007AFF) : const Color(0xFFE4E6EA),
          borderRadius: BorderRadius.circular(8),
        ),
        child: Text(word, style: const TextStyle(fontSize: 16)),
      ),
    );
  }
}
1
likes
160
points
149
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

Native text-selection menus and custom-painted handles, foundation-only.

Repository (GitLab)
View/report issues
Contributing

License

MIT (license)

Dependencies

flutter, flutter_web_plugins

More

Packages that depend on native_adaptive_toolbox

Packages that implement native_adaptive_toolbox