@innspel/react-native 0.9.5 → 0.10.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/dist/index.d.cts CHANGED
@@ -654,6 +654,19 @@ declare const tekst: {
654
654
  readonly bildeFjernet: "Bildet er fjernet.";
655
655
  readonly svarFra: (leietaker: string) => string;
656
656
  readonly svarFraGenerisk: "Svar";
657
+ readonly leggTilMer: "Legg til mer";
658
+ readonly leggTilMerLabel: "Legg til mer på denne saken";
659
+ readonly leggTilMerPlaceholder: "Hva vil du legge til?";
660
+ readonly leggTilMerHint: "Blir stående på saken din, under det du alt har skrevet. Ikke et svar på svaret — det blir ingen samtale her.";
661
+ readonly avbryt: "Avbryt";
662
+ readonly duLaTil: (dato: string) => string;
663
+ readonly lagtTil: "Lagt til";
664
+ readonly tilleggTak: "Du har lagt til tre ganger. Har du mer, send et nytt innspill.";
665
+ readonly tilleggLukket: "Saken er avsluttet — send et nytt innspill.";
666
+ /** F34: serverens egen setning (InnspelApiError) — aldri en rå feil. */
667
+ readonly tilleggFeil: (arsak: string) => string;
668
+ readonly tilleggIngenNett: "Ingen nett. Teksten din er beholdt — prøv igjen når du er på nett.";
669
+ readonly tilleggFeilGenerisk: "Kunne ikke legge til. Teksten din er beholdt — prøv igjen.";
657
670
  readonly sendNytt: "Send nytt innspill";
658
671
  /** Tom tilstand: det finnes ikke noe «nytt» å sende når ingenting er sendt. */
659
672
  readonly sendForste: "Send innspill";
package/dist/index.d.ts CHANGED
@@ -654,6 +654,19 @@ declare const tekst: {
654
654
  readonly bildeFjernet: "Bildet er fjernet.";
655
655
  readonly svarFra: (leietaker: string) => string;
656
656
  readonly svarFraGenerisk: "Svar";
657
+ readonly leggTilMer: "Legg til mer";
658
+ readonly leggTilMerLabel: "Legg til mer på denne saken";
659
+ readonly leggTilMerPlaceholder: "Hva vil du legge til?";
660
+ readonly leggTilMerHint: "Blir stående på saken din, under det du alt har skrevet. Ikke et svar på svaret — det blir ingen samtale her.";
661
+ readonly avbryt: "Avbryt";
662
+ readonly duLaTil: (dato: string) => string;
663
+ readonly lagtTil: "Lagt til";
664
+ readonly tilleggTak: "Du har lagt til tre ganger. Har du mer, send et nytt innspill.";
665
+ readonly tilleggLukket: "Saken er avsluttet — send et nytt innspill.";
666
+ /** F34: serverens egen setning (InnspelApiError) — aldri en rå feil. */
667
+ readonly tilleggFeil: (arsak: string) => string;
668
+ readonly tilleggIngenNett: "Ingen nett. Teksten din er beholdt — prøv igjen når du er på nett.";
669
+ readonly tilleggFeilGenerisk: "Kunne ikke legge til. Teksten din er beholdt — prøv igjen.";
657
670
  readonly sendNytt: "Send nytt innspill";
658
671
  /** Tom tilstand: det finnes ikke noe «nytt» å sende når ingenting er sendt. */
659
672
  readonly sendForste: "Send innspill";
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  // src/InnspelProvider.tsx
2
- import { createContext as createContext3, useCallback as useCallback3, useContext as useContext4, useMemo as useMemo4, useRef as useRef4, useState as useState5 } from "react";
2
+ import { createContext as createContext3, useCallback as useCallback3, useContext as useContext4, useMemo as useMemo4, useRef as useRef5, useState as useState5 } from "react";
3
3
  import { View as View9 } from "react-native";
4
4
 
5
5
  // src/skjermbilde.ts
@@ -352,17 +352,20 @@ function useTema() {
352
352
 
353
353
  // src/widget/Widget.tsx
354
354
  import {
355
- InnspelApiError,
355
+ InnspelApiError as InnspelApiError2,
356
356
  InnspelQueueFullError
357
357
  } from "@innspel/core";
358
- import { useCallback as useCallback2, useContext as useContext3, useEffect as useEffect2, useRef as useRef3, useState as useState4 } from "react";
359
- import { KeyboardAvoidingView, Modal, Pressable as Pressable5, View as View8 } from "react-native";
358
+ import { useCallback as useCallback2, useContext as useContext3, useEffect as useEffect2, useRef as useRef4, useState as useState4 } from "react";
359
+ import { KeyboardAvoidingView, Modal, Pressable as Pressable6, View as View8 } from "react-native";
360
360
  import { initialWindowMetrics, SafeAreaInsetsContext } from "react-native-safe-area-context";
361
361
 
362
362
  // src/Ikon.tsx
363
363
  import "react";
364
364
  import { SvgXml } from "react-native-svg";
365
365
 
366
+ // ../../node_modules/ionicons/dist/svg/add-outline.svg
367
+ var add_outline_default = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" class="ionicon"><path d="M256 112v288M400 256H112" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32px"/></svg>';
368
+
366
369
  // ../../node_modules/ionicons/dist/svg/arrow-back-outline.svg
367
370
  var arrow_back_outline_default = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" class="ionicon"><path d="M244 400 100 256l144-144M120 256h292" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="48px"/></svg>';
368
371
 
@@ -458,6 +461,7 @@ import { jsx } from "react/jsx-runtime";
458
461
  var utenTittel = (svg) => svg.replace(/<title>[\s\S]*?<\/title>/g, "");
459
462
  var IKONER = Object.fromEntries(
460
463
  Object.entries({
464
+ "add-outline": add_outline_default,
461
465
  "arrow-back-outline": arrow_back_outline_default,
462
466
  "brush-outline": brush_outline_default,
463
467
  // Fylte varianter: bare på sporvalgets to kort (DD-63), der ikonet bærer stemningen.
@@ -868,6 +872,20 @@ var tekst = {
868
872
  bildeFjernet: "Bildet er fjernet.",
869
873
  svarFra: (leietaker) => `Svar fra ${leietaker}`,
870
874
  svarFraGenerisk: "Svar",
875
+ // DD-69 — «Legg til mer»: innsenderens egne ord, ikke et svar på svaret.
876
+ leggTilMer: "Legg til mer",
877
+ leggTilMerLabel: "Legg til mer p\xE5 denne saken",
878
+ leggTilMerPlaceholder: "Hva vil du legge til?",
879
+ leggTilMerHint: "Blir st\xE5ende p\xE5 saken din, under det du alt har skrevet. Ikke et svar p\xE5 svaret \u2014 det blir ingen samtale her.",
880
+ avbryt: "Avbryt",
881
+ duLaTil: (dato) => `Du la til \xB7 ${dato}`,
882
+ lagtTil: "Lagt til",
883
+ tilleggTak: "Du har lagt til tre ganger. Har du mer, send et nytt innspill.",
884
+ tilleggLukket: "Saken er avsluttet \u2014 send et nytt innspill.",
885
+ /** F34: serverens egen setning (InnspelApiError) — aldri en rå feil. */
886
+ tilleggFeil: (arsak) => `Kunne ikke legge til: ${arsak} Teksten din er beholdt.`,
887
+ tilleggIngenNett: "Ingen nett. Teksten din er beholdt \u2014 pr\xF8v igjen n\xE5r du er p\xE5 nett.",
888
+ tilleggFeilGenerisk: "Kunne ikke legge til. Teksten din er beholdt \u2014 pr\xF8v igjen.",
871
889
  sendNytt: "Send nytt innspill",
872
890
  /** Tom tilstand: det finnes ikke noe «nytt» å sende når ingenting er sendt. */
873
891
  sendForste: "Send innspill",
@@ -1518,8 +1536,9 @@ function Kvittering({
1518
1536
  }
1519
1537
 
1520
1538
  // src/widget/MineInnspill.tsx
1521
- import { useEffect, useState as useState2 } from "react";
1522
- import { ActivityIndicator as ActivityIndicator2, ScrollView as ScrollView3, Text as Text6, View as View6 } from "react-native";
1539
+ import { InnspelApiError, NettverksFeil } from "@innspel/core";
1540
+ import { forwardRef, useEffect, useImperativeHandle, useRef as useRef2, useState as useState2 } from "react";
1541
+ import { ActivityIndicator as ActivityIndicator2, Keyboard, Pressable as Pressable4, ScrollView as ScrollView3, Text as Text6, TextInput as TextInput2, View as View6 } from "react-native";
1523
1542
 
1524
1543
  // src/widget/nyttSvar.ts
1525
1544
  var N\u00D8KKEL = "innspel:svar-sett";
@@ -1543,14 +1562,74 @@ async function markerSvarSett(storage, n\u00E5 = Date.now()) {
1543
1562
 
1544
1563
  // src/widget/MineInnspill.tsx
1545
1564
  import { jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
1546
- function MineInnspill({
1565
+ var MineInnspill = forwardRef(function MineInnspill2({
1547
1566
  rader,
1548
1567
  laster,
1549
1568
  tenantName,
1550
1569
  onNytt,
1551
- storage
1552
- }) {
1570
+ storage,
1571
+ leggTil,
1572
+ onOppdatert,
1573
+ minTextLength,
1574
+ textMaxLength,
1575
+ utkastLager
1576
+ }, ref) {
1553
1577
  const tema = useTema();
1578
+ const min = minTextLength ?? 10;
1579
+ const maks = textMaxLength ?? 280;
1580
+ const [apen, setApen] = useState2(null);
1581
+ const [utkast, setUtkast] = useState2("");
1582
+ const [sender, setSender] = useState2(false);
1583
+ const [feil, setFeil] = useState2();
1584
+ const [lagtTil, setLagtTil] = useState2();
1585
+ const egetLager = useRef2(/* @__PURE__ */ new Map());
1586
+ const lager = utkastLager ?? egetLager.current;
1587
+ const rulleRef = useRef2(null);
1588
+ const kortY = useRef2(/* @__PURE__ */ new Map());
1589
+ const apneFelt = (id) => {
1590
+ setApen(id);
1591
+ setUtkast(lager.get(id) ?? "");
1592
+ setFeil(void 0);
1593
+ setLagtTil(void 0);
1594
+ const y = kortY.current.get(id);
1595
+ if (y !== void 0) rulleRef.current?.scrollTo({ y, animated: true });
1596
+ };
1597
+ const lukkFelt = () => {
1598
+ if (apen === null) return false;
1599
+ Keyboard.dismiss();
1600
+ if (utkast.trim()) lager.set(apen, utkast);
1601
+ else lager.delete(apen);
1602
+ setApen(null);
1603
+ setUtkast("");
1604
+ setFeil(void 0);
1605
+ return true;
1606
+ };
1607
+ const avbryt = () => {
1608
+ if (apen !== null) lager.delete(apen);
1609
+ setApen(null);
1610
+ setUtkast("");
1611
+ setFeil(void 0);
1612
+ };
1613
+ useImperativeHandle(ref, () => ({ lukkFelt }));
1614
+ const send = async (id) => {
1615
+ if (!leggTil) return;
1616
+ setSender(true);
1617
+ setFeil(void 0);
1618
+ try {
1619
+ const sak = await leggTil(id, utkast.trim());
1620
+ onOppdatert?.(sak);
1621
+ lager.delete(id);
1622
+ setApen(null);
1623
+ setUtkast("");
1624
+ setLagtTil(id);
1625
+ } catch (e) {
1626
+ const kode = e instanceof InnspelApiError ? e.code : void 0;
1627
+ const tekstFeil = kode === "addendum_limit_reached" ? tekst.tilleggTak : kode === "submission_closed" ? tekst.tilleggLukket : e instanceof InnspelApiError && e.message ? tekst.tilleggFeil(e.message) : e instanceof NettverksFeil ? tekst.tilleggIngenNett : tekst.tilleggFeilGenerisk;
1628
+ setFeil({ id, tekst: tekstFeil });
1629
+ } finally {
1630
+ setSender(false);
1631
+ }
1632
+ };
1554
1633
  const [visning, setVisning] = useState2(() => rader.map((rad) => ({ rad, nytt: false })));
1555
1634
  useEffect(() => {
1556
1635
  let gjelder = true;
@@ -1568,84 +1647,200 @@ function MineInnspill({
1568
1647
  }, [storage, laster, rader]);
1569
1648
  return /* @__PURE__ */ jsxs6(View6, { style: { flex: 1, gap: tema.space.md }, children: [
1570
1649
  /* @__PURE__ */ jsx7(H, { sub: tekst.mineUnder, children: tekst.mineTittel }),
1571
- laster ? /* @__PURE__ */ jsx7(ActivityIndicator2, { accessibilityLabel: "Henter innspillene dine", color: tema.color.textSecondary }) : rader.length === 0 ? /* @__PURE__ */ jsx7(Hint, { children: tekst.mineTom }) : /* @__PURE__ */ jsx7(ScrollView3, { style: { flex: 1 }, contentContainerStyle: { gap: tema.space.sm }, children: visning.map(({ rad, nytt }) => {
1650
+ laster ? /* @__PURE__ */ jsx7(ActivityIndicator2, { accessibilityLabel: "Henter innspillene dine", color: tema.color.textSecondary }) : rader.length === 0 ? /* @__PURE__ */ jsx7(Hint, { children: tekst.mineTom }) : /* @__PURE__ */ jsx7(ScrollView3, { ref: rulleRef, style: { flex: 1 }, contentContainerStyle: { gap: tema.space.sm }, keyboardShouldPersistTaps: "handled", children: visning.map(({ rad, nytt }) => {
1572
1651
  const venter = rad.pending === true;
1573
1652
  const v = venter ? venterVisning : statusVisning(rad.status, rad.type, rad.deadlineAt);
1574
1653
  const nokkel = venter ? rad.clientRef : rad.id;
1654
+ const sakId = venter ? void 0 : rad.id;
1575
1655
  const svar = venter ? void 0 : rad.reply;
1576
- return /* @__PURE__ */ jsxs6(Card, { dashed: venter, children: [
1577
- /* @__PURE__ */ jsxs6(View6, { style: { flexDirection: "row", alignItems: "center", gap: tema.space.xs, flexWrap: "wrap" }, children: [
1578
- /* @__PURE__ */ jsx7(Hint, { children: venter ? `${tekst.mineIkkeSendt} \xB7 ${rad.type === "onske" ? tekst.onske : tekst.feil}` : tekst.sakHode(rad.type === "onske" ? tekst.onske : tekst.feil, rad.id) }),
1579
- nytt ? /* @__PURE__ */ jsx7(
1580
- Text6,
1581
- {
1582
- testID: "nytt-svar",
1583
- style: {
1584
- color: tema.color.info,
1585
- backgroundColor: tema.color.infoBg,
1586
- fontSize: tema.font.tiny,
1587
- fontWeight: tema.weight.semibold,
1588
- borderRadius: tema.radius.pill,
1589
- paddingHorizontal: tema.space.sm
1656
+ const tillegg = venter ? [] : rad.addenda ?? [];
1657
+ const kanLeggeTil = !venter && !!leggTil && rad.canAddAddendum === true;
1658
+ const taketN\u00E5dd = !venter && rad.canAddAddendum === false && tillegg.length >= 3;
1659
+ const feltApent = sakId !== void 0 && apen === sakId;
1660
+ const brukt = utkast.length;
1661
+ const full = brukt >= maks;
1662
+ const visTeller = brukt >= maks * 0.8;
1663
+ return /* @__PURE__ */ jsx7(
1664
+ View6,
1665
+ {
1666
+ onLayout: (e) => {
1667
+ if (sakId !== void 0) kortY.current.set(sakId, e.nativeEvent.layout.y);
1668
+ },
1669
+ children: /* @__PURE__ */ jsxs6(Card, { dashed: venter, children: [
1670
+ /* @__PURE__ */ jsxs6(View6, { style: { flexDirection: "row", alignItems: "center", gap: tema.space.xs, flexWrap: "wrap" }, children: [
1671
+ /* @__PURE__ */ jsx7(Hint, { children: venter ? `${tekst.mineIkkeSendt} \xB7 ${rad.type === "onske" ? tekst.onske : tekst.feil}` : tekst.sakHode(rad.type === "onske" ? tekst.onske : tekst.feil, rad.id) }),
1672
+ nytt ? /* @__PURE__ */ jsx7(
1673
+ Text6,
1674
+ {
1675
+ testID: "nytt-svar",
1676
+ style: {
1677
+ color: tema.color.info,
1678
+ backgroundColor: tema.color.infoBg,
1679
+ fontSize: tema.font.tiny,
1680
+ fontWeight: tema.weight.semibold,
1681
+ borderRadius: tema.radius.pill,
1682
+ paddingHorizontal: tema.space.sm
1683
+ },
1684
+ children: tekst.nyttSvar
1685
+ }
1686
+ ) : null
1687
+ ] }),
1688
+ /* @__PURE__ */ jsx7(
1689
+ Text6,
1690
+ {
1691
+ style: {
1692
+ color: tema.color.textPrimary,
1693
+ fontSize: tema.font.body,
1694
+ lineHeight: tema.lineHeight.body
1695
+ },
1696
+ children: rad.text
1697
+ }
1698
+ ),
1699
+ tillegg.length > 0 ? /* @__PURE__ */ jsx7(View6, { testID: `tillegg-${sakId}`, style: { gap: tema.space.xs }, children: tillegg.map((a) => /* @__PURE__ */ jsxs6(
1700
+ View6,
1701
+ {
1702
+ style: {
1703
+ borderLeftWidth: tema.hairline * 2,
1704
+ borderLeftColor: tema.color.border,
1705
+ paddingLeft: tema.space.sm,
1706
+ gap: tema.space.xs
1707
+ },
1708
+ children: [
1709
+ /* @__PURE__ */ jsxs6(View6, { style: { flexDirection: "row", alignItems: "center", gap: tema.space.xs }, children: [
1710
+ /* @__PURE__ */ jsx7(Ikon, { navn: "add-outline", storrelse: "minste", farge: tema.color.textSecondary }),
1711
+ /* @__PURE__ */ jsx7(Text6, { style: { color: tema.color.textSecondary, fontSize: tema.font.small }, children: tekst.duLaTil(formaterFrist(a.createdAt)) })
1712
+ ] }),
1713
+ /* @__PURE__ */ jsx7(Text6, { style: { color: tema.color.textPrimary, fontSize: tema.font.small, lineHeight: tema.lineHeight.small }, children: a.text })
1714
+ ]
1590
1715
  },
1591
- children: tekst.nyttSvar
1592
- }
1593
- ) : null
1594
- ] }),
1595
- /* @__PURE__ */ jsx7(
1596
- Text6,
1597
- {
1598
- style: {
1599
- color: tema.color.textPrimary,
1600
- fontSize: tema.font.body,
1601
- lineHeight: tema.lineHeight.body
1602
- },
1603
- children: rad.text
1604
- }
1605
- ),
1606
- /* @__PURE__ */ jsxs6(View6, { style: { gap: tema.space.xs }, children: [
1607
- /* @__PURE__ */ jsx7(StatusPill, { ikon: v.ikon, etikett: v.etikett, fg: v.fg, bg: v.bg }),
1608
- /* @__PURE__ */ jsx7(Hint, { children: v.lofte })
1609
- ] }),
1610
- !venter && rad.attachmentRemoved ? /* @__PURE__ */ jsxs6(View6, { testID: `bilde-fjernet-${rad.id}`, style: { flexDirection: "row", gap: tema.space.xs, alignItems: "center" }, children: [
1611
- /* @__PURE__ */ jsx7(Ikon, { navn: "trash-outline", storrelse: "minste", farge: tema.color.textMuted }),
1612
- /* @__PURE__ */ jsx7(Hint, { children: tekst.bildeFjernet })
1613
- ] }) : null,
1614
- svar ? /* @__PURE__ */ jsxs6(
1615
- View6,
1616
- {
1617
- style: {
1618
- borderLeftWidth: tema.hairline * 2,
1619
- borderLeftColor: tema.color.brand,
1620
- paddingLeft: tema.space.sm,
1621
- gap: tema.space.xs
1622
- },
1623
- children: [
1624
- /* @__PURE__ */ jsx7(Text6, { style: { color: tema.color.textSecondary, fontSize: tema.font.small }, children: tenantName ? tekst.svarFra(tenantName) : tekst.svarFraGenerisk }),
1716
+ a.id
1717
+ )) }) : null,
1718
+ /* @__PURE__ */ jsxs6(View6, { style: { gap: tema.space.xs }, children: [
1719
+ /* @__PURE__ */ jsx7(StatusPill, { ikon: v.ikon, etikett: v.etikett, fg: v.fg, bg: v.bg }),
1720
+ /* @__PURE__ */ jsx7(Hint, { children: v.lofte })
1721
+ ] }),
1722
+ sakId !== void 0 && lagtTil === sakId ? /* @__PURE__ */ jsx7(Text6, { accessibilityLiveRegion: "polite", testID: `lagt-til-${sakId}`, style: { color: tema.color.success, fontSize: tema.font.small }, children: tekst.lagtTil }) : null,
1723
+ feltApent ? /* @__PURE__ */ jsxs6(View6, { testID: `tillegg-felt-${sakId}`, style: { gap: tema.space.sm }, children: [
1724
+ /* @__PURE__ */ jsx7(Text6, { style: { color: tema.color.textSecondary, fontSize: tema.font.small }, children: tekst.leggTilMerLabel }),
1625
1725
  /* @__PURE__ */ jsx7(
1626
- Text6,
1726
+ TextInput2,
1627
1727
  {
1728
+ testID: "tillegg-tekst",
1729
+ accessibilityLabel: tekst.leggTilMerLabel,
1730
+ value: utkast,
1731
+ onChangeText: setUtkast,
1732
+ maxLength: maks,
1733
+ multiline: true,
1734
+ numberOfLines: 3,
1735
+ autoFocus: true,
1736
+ placeholder: tekst.leggTilMerPlaceholder,
1737
+ placeholderTextColor: tema.color.textMuted,
1628
1738
  style: {
1739
+ backgroundColor: tema.color.surfaceRaised,
1740
+ borderWidth: tema.hairline,
1741
+ borderColor: full ? tema.color.warning : tema.color.borderStrong,
1742
+ borderRadius: tema.radius.md,
1743
+ padding: tema.space.md,
1629
1744
  color: tema.color.textPrimary,
1630
- fontSize: tema.font.small,
1631
- lineHeight: tema.lineHeight.small
1632
- },
1633
- children: svar.text
1745
+ fontSize: tema.font.body,
1746
+ lineHeight: tema.lineHeight.body,
1747
+ minHeight: tema.touch * 2,
1748
+ textAlignVertical: "top"
1749
+ }
1634
1750
  }
1635
- )
1636
- ]
1637
- }
1638
- ) : null
1639
- ] }, nokkel);
1751
+ ),
1752
+ visTeller ? /* @__PURE__ */ jsxs6(
1753
+ Text6,
1754
+ {
1755
+ testID: "tillegg-teller",
1756
+ accessibilityLiveRegion: full ? "assertive" : "polite",
1757
+ style: { alignSelf: "flex-end", color: full ? tema.color.warning : tema.color.textMuted, fontSize: tema.font.small },
1758
+ children: [
1759
+ brukt,
1760
+ " / ",
1761
+ maks,
1762
+ full ? tekst.tellerFull : ""
1763
+ ]
1764
+ }
1765
+ ) : null,
1766
+ feil?.id === sakId ? /* @__PURE__ */ jsx7(Text6, { accessibilityLiveRegion: "assertive", testID: `tillegg-feil-${sakId}`, style: { color: tema.color.danger, fontSize: tema.font.small, lineHeight: tema.lineHeight.small }, children: feil.tekst }) : null
1767
+ ] }) : kanLeggeTil ? /* @__PURE__ */ jsxs6(
1768
+ Pressable4,
1769
+ {
1770
+ testID: `legg-til-mer-${sakId}`,
1771
+ accessibilityRole: "button",
1772
+ accessibilityLabel: tekst.leggTilMerLabel,
1773
+ onPress: () => sakId !== void 0 && apneFelt(sakId),
1774
+ style: { alignSelf: "flex-start", minHeight: tema.touch, flexDirection: "row", alignItems: "center", gap: tema.space.xs, paddingHorizontal: tema.space.sm },
1775
+ children: [
1776
+ /* @__PURE__ */ jsx7(Ikon, { navn: "add-outline", storrelse: "minste", farge: tema.color.actionSecondaryText }),
1777
+ /* @__PURE__ */ jsx7(Text6, { style: { color: tema.color.actionSecondaryText, fontSize: tema.font.body, fontWeight: tema.weight.semibold }, children: tekst.leggTilMer })
1778
+ ]
1779
+ }
1780
+ ) : taketN\u00E5dd ? /* @__PURE__ */ jsx7(Hint, { children: tekst.tilleggTak }) : null,
1781
+ !venter && rad.attachmentRemoved ? /* @__PURE__ */ jsxs6(View6, { testID: `bilde-fjernet-${rad.id}`, style: { flexDirection: "row", gap: tema.space.xs, alignItems: "center" }, children: [
1782
+ /* @__PURE__ */ jsx7(Ikon, { navn: "trash-outline", storrelse: "minste", farge: tema.color.textMuted }),
1783
+ /* @__PURE__ */ jsx7(Hint, { children: tekst.bildeFjernet })
1784
+ ] }) : null,
1785
+ svar ? /* @__PURE__ */ jsxs6(
1786
+ View6,
1787
+ {
1788
+ style: {
1789
+ borderLeftWidth: tema.hairline * 2,
1790
+ borderLeftColor: tema.color.brand,
1791
+ paddingLeft: tema.space.sm,
1792
+ gap: tema.space.xs
1793
+ },
1794
+ children: [
1795
+ /* @__PURE__ */ jsx7(Text6, { style: { color: tema.color.textSecondary, fontSize: tema.font.small }, children: tenantName ? tekst.svarFra(tenantName) : tekst.svarFraGenerisk }),
1796
+ /* @__PURE__ */ jsx7(
1797
+ Text6,
1798
+ {
1799
+ style: {
1800
+ color: tema.color.textPrimary,
1801
+ fontSize: tema.font.small,
1802
+ lineHeight: tema.lineHeight.small
1803
+ },
1804
+ children: svar.text
1805
+ }
1806
+ )
1807
+ ]
1808
+ }
1809
+ ) : null
1810
+ ] })
1811
+ },
1812
+ nokkel
1813
+ );
1640
1814
  }) }),
1641
- /* @__PURE__ */ jsx7(Btn, { testID: "send-nytt", label: rader.length === 0 ? tekst.sendForste : tekst.sendNytt, onPress: onNytt })
1815
+ apen !== null ? (
1816
+ // F32: fotens plass — én primærhandling om gangen. «Send nytt innspill» er borte til
1817
+ // tillegget er sendt eller avbrutt, så første trykk aldri treffer feil knapp.
1818
+ /* @__PURE__ */ jsxs6(View6, { testID: "tillegg-fot", style: { gap: tema.space.sm }, children: [
1819
+ /* @__PURE__ */ jsxs6(View6, { style: { flexDirection: "row", gap: tema.space.sm }, children: [
1820
+ /* @__PURE__ */ jsx7(
1821
+ Btn,
1822
+ {
1823
+ testID: "tillegg-send",
1824
+ label: sender ? tekst.sender : tekst.send,
1825
+ disabled: utkast.length < min || sender,
1826
+ busy: sender,
1827
+ ...utkast.length < min ? { hint: tekst.forKort(min) } : {},
1828
+ onPress: () => void send(apen),
1829
+ grow: true
1830
+ }
1831
+ ),
1832
+ /* @__PURE__ */ jsx7(Btn, { testID: "tillegg-avbryt", label: tekst.avbryt, variant: "secondary", onPress: avbryt, grow: true })
1833
+ ] }),
1834
+ /* @__PURE__ */ jsx7(Hint, { children: tekst.leggTilMerHint })
1835
+ ] })
1836
+ ) : /* @__PURE__ */ jsx7(Btn, { testID: "send-nytt", label: rader.length === 0 ? tekst.sendForste : tekst.sendNytt, onPress: onNytt })
1642
1837
  ] });
1643
- }
1838
+ });
1644
1839
 
1645
1840
  // src/widget/Skjermbilde.tsx
1646
1841
  import { skaler, strokTilRekter, treffer, trykkTilRekt } from "@innspel/core";
1647
- import { useCallback, useMemo as useMemo3, useRef as useRef2, useState as useState3 } from "react";
1648
- import { Image as Image2, Pressable as Pressable4, StyleSheet, Text as Text7, View as View7 } from "react-native";
1842
+ import { useCallback, useMemo as useMemo3, useRef as useRef3, useState as useState3 } from "react";
1843
+ import { Image as Image2, Pressable as Pressable5, StyleSheet, Text as Text7, View as View7 } from "react-native";
1649
1844
  import { jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
1650
1845
  function Skjermbilde({
1651
1846
  bilde,
@@ -1663,7 +1858,7 @@ function Skjermbilde({
1663
1858
  const [allTekst, setAllTekst] = useState3(false);
1664
1859
  const [egne, setEgne] = useState3([]);
1665
1860
  const [ramme, setRamme] = useState3(null);
1666
- const strok = useRef2([]);
1861
+ const strok = useRef3([]);
1667
1862
  const visning = useMemo3(() => {
1668
1863
  if (!ramme) return null;
1669
1864
  const s = Math.min(ramme.width / bilde.width, ramme.height / bilde.height);
@@ -1706,7 +1901,7 @@ function Skjermbilde({
1706
1901
  const knapp = (id, ikon, label) => {
1707
1902
  const valgt = id === "allTekst" ? allTekst : verktoy === id && !allTekst;
1708
1903
  return /* @__PURE__ */ jsxs7(
1709
- Pressable4,
1904
+ Pressable5,
1710
1905
  {
1711
1906
  testID: `verktoy-${id}`,
1712
1907
  accessibilityRole: "button",
@@ -1887,7 +2082,7 @@ function Widget({
1887
2082
  const tema = useTema();
1888
2083
  const insets = useContext3(SafeAreaInsetsContext) ?? initialWindowMetrics?.insets ?? { top: 0, bottom: 0, left: 0, right: 0 };
1889
2084
  const skjermbilde = useSkjermbilde();
1890
- const previewRef = useRef3(null);
2085
+ const previewRef = useRef4(null);
1891
2086
  const [steg, setSteg] = useState4("valg");
1892
2087
  const [spor, setSpor] = useState4("feil");
1893
2088
  const [verdi, setVerdi] = useState4("");
@@ -1930,7 +2125,7 @@ function Widget({
1930
2125
  }, []);
1931
2126
  const [mineDirekte, setMineDirekte] = useState4(false);
1932
2127
  const [stegForMine, setStegForMine] = useState4("valg");
1933
- const stegRef = useRef3(steg);
2128
+ const stegRef = useRef4(steg);
1934
2129
  stegRef.current = steg;
1935
2130
  const harUtkast = verdi.trim().length > 0 || vedlegg !== void 0;
1936
2131
  const lukk = useCallback2(() => {
@@ -2051,9 +2246,12 @@ function Widget({
2051
2246
  setVedlegg(void 0);
2052
2247
  setSteg("felt");
2053
2248
  }, []);
2249
+ const mineRef = useRef4(null);
2250
+ const tilleggUtkast = useRef4(/* @__PURE__ */ new Map());
2054
2251
  const gaaTilbake = useCallback2(() => {
2055
2252
  setFeil(void 0);
2056
2253
  if (steg === "mine") {
2254
+ if (mineRef.current?.lukkFelt()) return;
2057
2255
  if (mineDirekte) lukk();
2058
2256
  else setSteg("valg");
2059
2257
  return;
@@ -2101,7 +2299,7 @@ function Widget({
2101
2299
  onSubmitted?.(res.status === "queued" ? { type: spor, queued: true } : { type: spor, queued: false, id: res.submission.id });
2102
2300
  } catch (e) {
2103
2301
  if (e instanceof InnspelQueueFullError) setFeil(e.userMessage);
2104
- else if (e instanceof InnspelApiError) setFeil(e.message);
2302
+ else if (e instanceof InnspelApiError2) setFeil(e.message);
2105
2303
  else setFeil("tjeneren svarte ikke.");
2106
2304
  } finally {
2107
2305
  setSender(false);
@@ -2115,7 +2313,7 @@ function Widget({
2115
2313
  setKvittering({ art: "jaOgsa", nr: res.id });
2116
2314
  setSteg("kvittering");
2117
2315
  } catch (e) {
2118
- setFeil(e instanceof InnspelApiError ? e.message : "tjeneren svarte ikke.");
2316
+ setFeil(e instanceof InnspelApiError2 ? e.message : "tjeneren svarte ikke.");
2119
2317
  } finally {
2120
2318
  setSender(false);
2121
2319
  }
@@ -2183,7 +2381,7 @@ function Widget({
2183
2381
  /* @__PURE__ */ jsxs8(View8, { style: { flexDirection: "row", justifyContent: "space-between", alignItems: "center" }, children: [
2184
2382
  steg !== "valg" && steg !== "kvittering" ? /* @__PURE__ */ jsx9(Btn, { variant: "ghost", testID: "tilbake", ikon: "arrow-back-outline", label: tekst.tilbake, onPress: gaaTilbake }) : /* @__PURE__ */ jsx9(View8, {}),
2185
2383
  /* @__PURE__ */ jsx9(
2186
- Pressable5,
2384
+ Pressable6,
2187
2385
  {
2188
2386
  testID: "lukk",
2189
2387
  accessibilityRole: "button",
@@ -2276,11 +2474,17 @@ function Widget({
2276
2474
  steg === "mine" ? /* @__PURE__ */ jsx9(
2277
2475
  MineInnspill,
2278
2476
  {
2477
+ ref: mineRef,
2478
+ utkastLager: tilleggUtkast.current,
2279
2479
  storage: client.storage,
2280
2480
  rader: mine,
2281
2481
  laster: lasterMine,
2282
2482
  ...tenantName ? { tenantName } : {},
2283
- onNytt: nyttFraMine
2483
+ onNytt: nyttFraMine,
2484
+ leggTil: (id, text) => client.subjects.addAddendum(id, text),
2485
+ onOppdatert: (sak) => setMine((liste) => liste.map((r) => r.pending !== true && r.id === sak.id ? sak : r)),
2486
+ minTextLength: innstillinger.minTextLength,
2487
+ textMaxLength: innstillinger.textMaxLength
2284
2488
  }
2285
2489
  ) : null
2286
2490
  ]
@@ -2325,8 +2529,8 @@ function InnspelProvider({
2325
2529
  }) {
2326
2530
  const [synlig, setSynlig] = useState5(false);
2327
2531
  const [apneOpsjoner, setApneOpsjoner] = useState5();
2328
- const registrert = useRef4(false);
2329
- const rootRef = useRef4(null);
2532
+ const registrert = useRef5(false);
2533
+ const rootRef = useRef5(null);
2330
2534
  const privat = useMemo4(createPrivateRegistry, []);
2331
2535
  const open = useCallback3(
2332
2536
  async (options) => {
@@ -2373,11 +2577,11 @@ function useInnspel() {
2373
2577
  }
2374
2578
 
2375
2579
  // src/InnspelPrivate.tsx
2376
- import { useEffect as useEffect3, useRef as useRef5 } from "react";
2580
+ import { useEffect as useEffect3, useRef as useRef6 } from "react";
2377
2581
  import { View as View10 } from "react-native";
2378
2582
  import { jsx as jsx11 } from "react/jsx-runtime";
2379
2583
  function InnspelPrivate({ children, style }) {
2380
- const ref = useRef5(null);
2584
+ const ref = useRef6(null);
2381
2585
  const kontekst = useSkjermbilde();
2382
2586
  const registrer = kontekst?.privat.register;
2383
2587
  useEffect3(() => {
@@ -2389,14 +2593,14 @@ function InnspelPrivate({ children, style }) {
2389
2593
 
2390
2594
  // src/program/InnspelProgram.tsx
2391
2595
  import {
2392
- InnspelApiError as InnspelApiError2
2596
+ InnspelApiError as InnspelApiError3
2393
2597
  } from "@innspel/core";
2394
2598
  import { useCallback as useCallback4, useEffect as useEffect4, useState as useState7 } from "react";
2395
- import { Modal as Modal4, Pressable as Pressable8, Text as Text12, View as View15 } from "react-native";
2599
+ import { Modal as Modal4, Pressable as Pressable9, Text as Text12, View as View15 } from "react-native";
2396
2600
 
2397
2601
  // src/program/Aksept.tsx
2398
2602
  import "react";
2399
- import { Linking, Modal as Modal2, Pressable as Pressable6, ScrollView as ScrollView4, Text as Text8, View as View11 } from "react-native";
2603
+ import { Linking, Modal as Modal2, Pressable as Pressable7, ScrollView as ScrollView4, Text as Text8, View as View11 } from "react-native";
2400
2604
 
2401
2605
  // src/program/periode.ts
2402
2606
  var MANEDER = [
@@ -2517,7 +2721,7 @@ function Aksept({
2517
2721
  ] }),
2518
2722
  program.termsUrl && !program.commitmentFree ? /* @__PURE__ */ jsxs10(View11, { style: { gap: tema.space.xs }, children: [
2519
2723
  /* @__PURE__ */ jsx12(
2520
- Pressable6,
2724
+ Pressable7,
2521
2725
  {
2522
2726
  testID: "vilkar-lenke",
2523
2727
  accessibilityRole: "link",
@@ -2566,7 +2770,7 @@ function Aksept({
2566
2770
 
2567
2771
  // src/program/HvordanGikkDet.tsx
2568
2772
  import { useState as useState6 } from "react";
2569
- import { Modal as Modal3, Pressable as Pressable7, Text as Text9, TextInput as TextInput2, View as View12 } from "react-native";
2773
+ import { Modal as Modal3, Pressable as Pressable8, Text as Text9, TextInput as TextInput3, View as View12 } from "react-native";
2570
2774
  import { jsx as jsx13, jsxs as jsxs11 } from "react/jsx-runtime";
2571
2775
  var UTFALL = [
2572
2776
  { verdi: "fungerte", ikon: "checkmark-circle-outline", etikett: tekst.utfallFungerte },
@@ -2607,7 +2811,7 @@ function HvordanGikkDet({
2607
2811
  /* @__PURE__ */ jsx13(View12, { testID: "utfall-gruppe", accessibilityRole: "radiogroup", accessibilityLabel: tekst.hvordanGikkDet, style: { flexDirection: "row", gap: tema.space.sm }, children: UTFALL.map((u) => {
2608
2812
  const aktiv = valgt === u.verdi;
2609
2813
  return /* @__PURE__ */ jsxs11(
2610
- Pressable7,
2814
+ Pressable8,
2611
2815
  {
2612
2816
  testID: `utfall-${u.verdi}`,
2613
2817
  accessibilityRole: "radio",
@@ -2637,7 +2841,7 @@ function HvordanGikkDet({
2637
2841
  /* @__PURE__ */ jsxs11(View12, { style: { gap: tema.space.xs }, children: [
2638
2842
  /* @__PURE__ */ jsx13(Text9, { style: { color: tema.color.textSecondary, fontSize: tema.font.small }, children: tekst.setningLabel }),
2639
2843
  /* @__PURE__ */ jsx13(
2640
- TextInput2,
2844
+ TextInput3,
2641
2845
  {
2642
2846
  testID: "hvordan-setning",
2643
2847
  accessibilityLabel: tekst.setningLabel,
@@ -2838,7 +3042,7 @@ function InnspelProgram({
2838
3042
  }
2839
3043
  setDialog(void 0);
2840
3044
  } catch (e) {
2841
- setSvarFeil(e instanceof InnspelApiError2 ? e.message : "tjeneren svarte ikke.");
3045
+ setSvarFeil(e instanceof InnspelApiError3 ? e.message : "tjeneren svarte ikke.");
2842
3046
  } finally {
2843
3047
  setSender(false);
2844
3048
  }
@@ -2867,7 +3071,7 @@ function InnspelProgram({
2867
3071
  setViserAksept(false);
2868
3072
  onEndring?.(ny);
2869
3073
  } catch (e) {
2870
- setFeil(e instanceof InnspelApiError2 ? e.message : "tjeneren svarte ikke.");
3074
+ setFeil(e instanceof InnspelApiError3 ? e.message : "tjeneren svarte ikke.");
2871
3075
  } finally {
2872
3076
  setSender(false);
2873
3077
  }
@@ -2881,7 +3085,7 @@ function InnspelProgram({
2881
3085
  setViserGaaUt(false);
2882
3086
  onEndring?.(null);
2883
3087
  } catch (e) {
2884
- setFeil(e instanceof InnspelApiError2 ? e.message : "tjeneren svarte ikke.");
3088
+ setFeil(e instanceof InnspelApiError3 ? e.message : "tjeneren svarte ikke.");
2885
3089
  } finally {
2886
3090
  setSender(false);
2887
3091
  }
@@ -3010,7 +3214,7 @@ function InnspelProgram({
3010
3214
  /* @__PURE__ */ jsx16(Hint, { children: tekst.invitasjonKontakter(program.maxContacts) })
3011
3215
  ] }),
3012
3216
  /* @__PURE__ */ jsx16(
3013
- Pressable8,
3217
+ Pressable9,
3014
3218
  {
3015
3219
  testID: "avvis-invitasjon",
3016
3220
  accessibilityRole: "button",